if not WeakAuras.IsLibsOK() then return end ---@type string local AddonName = ... ---@class OptionsPrivate local OptionsPrivate = select(2, ...) -- Lua APIs local tinsert, tremove, wipe = table.insert, table.remove, wipe local pairs, type = pairs, type local error = error local coroutine = coroutine local _G = _G -- WoW APIs local InCombatLockdown = InCombatLockdown local CreateFrame = CreateFrame local AceGUI = LibStub("AceGUI-3.0") ---@class WeakAuras local WeakAuras = WeakAuras local L = WeakAuras.L local ADDON_NAME = "WeakAurasOptions"; local displayButtons = {}; OptionsPrivate.displayButtons = displayButtons; local spellCache = WeakAuras.spellCache; local savedVars = {}; OptionsPrivate.savedVars = savedVars; OptionsPrivate.expanderAnchors = {} OptionsPrivate.expanderButtons = {} local collapsedOptions = {} local collapsed = {} -- magic value local tempGroup = { id = {"tempGroup"}, regionType = "group", controlledChildren = {}, load = {}, triggers = {{}}, config = {}, authorOptions = {}, anchorPoint = "CENTER", anchorFrameType = "SCREEN", xOffset = 0, yOffset = 0 }; OptionsPrivate.tempGroup = tempGroup; -- Does not duplicate child auras. function OptionsPrivate.DuplicateAura(data, newParent, massEdit, targetIndex) local base_id = data.id .. " " local num = 2 -- if the old id ends with a number increment the number local matchName, matchNumber = string.match(data.id, "^(.-)(%d*)$") matchNumber = tonumber(matchNumber) if (matchName ~= "" and matchNumber ~= nil) then base_id = matchName num = matchNumber + 1 end local new_id = base_id .. num while(WeakAuras.GetData(new_id)) do new_id = base_id .. num num = num + 1 end local newData = CopyTable(data) newData.id = new_id newData.parent = nil newData.uid = WeakAuras.GenerateUniqueID() if newData.controlledChildren then newData.controlledChildren = {} end WeakAuras.Add(newData) WeakAuras.NewDisplayButton(newData, massEdit) if(newParent or data.parent) then local parentId = newParent or data.parent local parentData = WeakAuras.GetData(parentId) local index if targetIndex then index = targetIndex elseif newParent then index = #parentData.controlledChildren + 1 else index = tIndexOf(parentData.controlledChildren, data.id) + 1 end if(index) then tinsert(parentData.controlledChildren, index, newData.id) newData.parent = parentId WeakAuras.Add(newData) WeakAuras.Add(parentData) OptionsPrivate.Private.AddParents(parentData) for index, id in pairs(parentData.controlledChildren) do local childButton = OptionsPrivate.GetDisplayButton(id) childButton:SetGroup(parentData.id, parentData.regionType == "dynamicgroup") childButton:SetGroupOrder(index, #parentData.controlledChildren) end if not massEdit then local button = OptionsPrivate.GetDisplayButton(parentData.id) button.callbacks.UpdateExpandButton() button:UpdateParentWarning() end OptionsPrivate.ClearOptions(parentData.id) end end return newData end AceGUI:RegisterLayout("AbsoluteList", function(content, children) local yOffset = 0; for i = 1, #children do local child = children[i] local frame = child.frame; frame:ClearAllPoints(); frame:Show(); frame:SetPoint("LEFT", content); frame:SetPoint("RIGHT", content); frame:SetPoint("TOP", content, "TOP", 0, yOffset) if child.DoLayout then child:DoLayout() end yOffset = yOffset - ((frame.height or frame:GetHeight() or 0) + 2); end if(content.obj.LayoutFinished) then content.obj:LayoutFinished(nil, yOffset * -1); end end); AceGUI:RegisterLayout("ButtonsScrollLayout", function(content, children, skipLayoutFinished) local yOffset = 0 local scrollTop, scrollBottom = content.obj:GetScrollPos() for i = 1, #children do local child = children[i] local frame = child.frame; if not child.dragging then local frameHeight = (frame.height or frame:GetHeight() or 0); frame:ClearAllPoints(); if (-yOffset + frameHeight > scrollTop and -yOffset - frameHeight < scrollBottom) then frame:Show(); frame:SetPoint("LEFT", content); frame:SetPoint("RIGHT", content); frame:SetPoint("TOP", content, "TOP", 0, yOffset) else frame:Hide(); frame.yOffset = yOffset end yOffset = yOffset - (frameHeight + 2); end if child.DoLayout then child:DoLayout() end end if(content.obj.LayoutFinished and not skipLayoutFinished) then content.obj:LayoutFinished(nil, yOffset * -1) end end) function OptionsPrivate.MultipleDisplayTooltipDesc() local desc = {{L["Multiple Displays"], L["Temporary Group"]}}; for index, id in pairs(tempGroup.controlledChildren) do desc[index + 1] = {" ", id}; end desc[2][1] = L["Children:"] tinsert(desc, " "); tinsert(desc, {" ", "|cFF00FFFF"..L["Right-click for more options"]}); tinsert(desc, {" ", "|cFF00FFFF"..L["Drag to move"]}); return desc; end local frame; local db; local odb; --- @type boolean? local reopenAfterCombat = false; local loadedFrame = CreateFrame("Frame"); loadedFrame:RegisterEvent("ADDON_LOADED"); loadedFrame:RegisterEvent("PLAYER_REGEN_ENABLED"); loadedFrame:RegisterEvent("PLAYER_REGEN_DISABLED"); loadedFrame:SetScript("OnEvent", function(self, event, addon) if (event == "ADDON_LOADED") then if(addon == ADDON_NAME) then db = WeakAurasSaved; WeakAurasOptionsSaved = WeakAurasOptionsSaved or {}; odb = WeakAurasOptionsSaved; -- Remove icon and id cache (replaced with spellCache) if (odb.iconCache) then odb.iconCache = nil; end if (odb.idCache) then odb.idCache = nil; end odb.spellCache = odb.spellCache or {}; spellCache.Load(odb); if odb.magnetAlign == nil then odb.magnetAlign = true end if db.import_disabled then db.import_disabled = nil end savedVars.db = db; savedVars.odb = odb; end elseif (event == "PLAYER_REGEN_DISABLED") then if(frame and frame:IsVisible()) then reopenAfterCombat = true; WeakAuras.HideOptions(); end elseif (event == "PLAYER_REGEN_ENABLED") then if (reopenAfterCombat) then reopenAfterCombat = nil; WeakAuras.ShowOptions() end end end); local function addParents(hash, data) local parent = data.parent if parent then hash[parent] = true local parentData = WeakAuras.GetData(parent) if parentData then addParents(hash, parentData) end end end local function commonParent(controlledChildren) local allSame = true local parent = nil local targetIndex = math.huge for index, id in ipairs(controlledChildren) do local childData = WeakAuras.GetData(id); local childButton = OptionsPrivate.GetDisplayButton(id) targetIndex = min(targetIndex, childButton:GetGroupOrder() or math.huge) if (parent == nil) then parent = childData.parent elseif not childData.parent then allSame = false elseif childData.parent ~= parent then allSame = false end end if allSame then return parent, targetIndex end end local function CreateNewGroupFromSelection(regionType, resetChildPositions) local data = { id = OptionsPrivate.Private.FindUnusedId(tempGroup.controlledChildren[1].." Group"), regionType = regionType, }; WeakAuras.DeepMixin(data, OptionsPrivate.Private.data_stub) data.internalVersion = WeakAuras.InternalVersion() OptionsPrivate.Private.validate(data, OptionsPrivate.Private.regionTypes[regionType].default); local parent, targetIndex = commonParent(tempGroup.controlledChildren) if (parent) then local parentData = WeakAuras.GetData(parent) tinsert(parentData.controlledChildren, targetIndex, data.id) data.parent = parent WeakAuras.Add(data); WeakAuras.Add(parentData); OptionsPrivate.Private.AddParents(parentData) WeakAuras.NewDisplayButton(data); WeakAuras.UpdateGroupOrders(parentData); OptionsPrivate.ClearOptions(parentData.id); local parentButton = OptionsPrivate.GetDisplayButton(parent) parentButton.callbacks.UpdateExpandButton(); parentButton:Expand(); parentButton:ReloadTooltip(); parentButton:UpdateParentWarning(); else WeakAuras.Add(data); WeakAuras.NewDisplayButton(data); end for index, childId in pairs(tempGroup.controlledChildren) do local childData = WeakAuras.GetData(childId); local childButton = OptionsPrivate.GetDisplayButton(childId) local oldParent = childData.parent local oldParentData = WeakAuras.GetData(oldParent) if (oldParent) then local oldIndex = childButton:GetGroupOrder() tremove(oldParentData.controlledChildren, oldIndex) WeakAuras.Add(oldParentData) OptionsPrivate.Private.AddParents(oldParentData) WeakAuras.UpdateGroupOrders(oldParentData); WeakAuras.ClearAndUpdateOptions(oldParent); local oldParentButton = OptionsPrivate.GetDisplayButton(oldParent) oldParentButton.callbacks.UpdateExpandButton(); oldParentButton:ReloadTooltip() oldParentButton:UpdateParentWarning() end tinsert(data.controlledChildren, childId); childData.parent = data.id; if resetChildPositions then childData.xOffset = 0; childData.yOffset = 0; end WeakAuras.Add(data); WeakAuras.Add(childData); OptionsPrivate.ClearOptions(childData.id) childButton:SetGroup(data.id, data.regionType == "dynamicgroup"); childButton:SetGroupOrder(index, #data.controlledChildren); end local button = OptionsPrivate.GetDisplayButton(data.id); button.callbacks.UpdateExpandButton(); button:UpdateParentWarning() OptionsPrivate.SortDisplayButtons(); button:Expand(); if data.parent then OptionsPrivate.Private.AddParents(data) end end function OptionsPrivate.MultipleDisplayTooltipMenu() local frame = frame; local menu = { { text = L["Add to new Group"], notCheckable = 1, func = function() CreateNewGroupFromSelection("group") end }, { text = L["Add to new Dynamic Group"], notCheckable = 1, func = function() CreateNewGroupFromSelection("dynamicgroup", true) end }, { text = L["Duplicate All"], notCheckable = 1, func = function() local duplicated = {}; for child in OptionsPrivate.Private.TraverseAllChildren(tempGroup) do local newData = OptionsPrivate.DuplicateAura(child) tinsert(duplicated, newData.id); end OptionsPrivate.ClearPicks(); frame:PickDisplayBatch(duplicated); end }, { text = " ", notCheckable = 1, notClickable = 1 }, { text = L["Delete all"], notCheckable = 1, func = function() local toDelete = {}; local parents = {}; for child in OptionsPrivate.Private.TraverseAllChildren(tempGroup) do tinsert(toDelete, child) addParents(parents, child) end OptionsPrivate.ConfirmDelete(toDelete, parents) end }, { text = " ", notClickable = 1, notCheckable = 1, }, { text = L["Close"], notCheckable = 1, func = function() WeakAuras_DropDownMenu:Hide() end } }; local anyGroup = false; local allSameParent = true local commonParent = nil local first = true for _, id in pairs(tempGroup.controlledChildren) do local childData = WeakAuras.GetData(id); if(childData and childData.controlledChildren) then anyGroup = true; end if (first) then commonParent = childData.parent first = false elseif childData.parent ~= commonParent then allSameParent = false end end if(anyGroup) then -- Disable "Add to New Dynamic Group" menu[2].notClickable = 1; menu[2].text = "|cFF777777"..menu[2].text; end -- Also disable Add to New Dynamic Group/Group if that would create -- a group inside a dynamic group if (allSameParent and commonParent) then local parentData = WeakAuras.GetData(commonParent); if (parentData and parentData.regionType == "dynamicgroup") then menu[1].notClickable = 1; menu[1].text = "|cFF777777"..menu[1].text; menu[2].notClickable = 1; menu[2].text = "|cFF777777"..menu[1].text; end end return menu; end StaticPopupDialogs["WEAKAURAS_CONFIRM_DELETE"] = { text = "", button1 = L["Delete"], button2 = L["Cancel"], OnAccept = function(self) if self.data then OptionsPrivate.DeleteAuras(self.data.toDelete, self.data.parents) end end, OnCancel = function(self) self.data = nil end, showAlert = true, whileDead = true, preferredindex = 4, } function OptionsPrivate.IsWagoUpdateIgnored(auraId) local auraData = WeakAuras.GetData(auraId) if auraData then for child in OptionsPrivate.Private.TraverseAll(auraData) do if child.ignoreWagoUpdate then return true end end end return false end function OptionsPrivate.HasWagoUrl(auraId) local auraData = WeakAuras.GetData(auraId) if auraData then for child in OptionsPrivate.Private.TraverseAll(auraData) do if child.url and child.url ~= "" then return true end end end return false end function OptionsPrivate.ConfirmDelete(toDelete, parents) if toDelete then local warningForm = L["You are about to delete %d aura(s). |cFFFF0000This cannot be undone!|r Would you like to continue?"] StaticPopupDialogs["WEAKAURAS_CONFIRM_DELETE"].text = warningForm:format(#toDelete) StaticPopup_Show("WEAKAURAS_CONFIRM_DELETE", "", "", {toDelete = toDelete, parents = parents}) end end local function AfterScanForLoads() if(frame) then if (frame:IsVisible()) then OptionsPrivate.SortDisplayButtons(nil, true); else frame.needsSort = true; end end end local function OnAboutToDelete(event, uid, id, parentUid, parentId) local data = OptionsPrivate.Private.GetDataByUID(uid) if(data.controlledChildren) then for index, childId in pairs(data.controlledChildren) do local childButton = displayButtons[childId]; if(childButton) then childButton:SetGroup(); end local childData = db.displays[childId]; if(childData) then childData.parent = nil; end end end OptionsPrivate.Private.CollapseAllClones(id); OptionsPrivate.ClearOptions(id) frame:ClearPicks(); if(displayButtons[id])then frame.buttonsScroll:DeleteChild(displayButtons[id]); displayButtons[id] = nil; end collapsedOptions[id] = nil end local function OnRename(event, uid, oldid, newid) local data = OptionsPrivate.Private.GetDataByUID(uid) OptionsPrivate.displayButtons[newid] = OptionsPrivate.displayButtons[oldid]; OptionsPrivate.displayButtons[newid]:SetData(data) OptionsPrivate.displayButtons[oldid] = nil; OptionsPrivate.ClearOptions(oldid) OptionsPrivate.displayButtons[newid]:SetTitle(newid); collapsedOptions[newid] = collapsedOptions[oldid] collapsedOptions[oldid] = nil if(data.controlledChildren) then for _, childId in pairs(data.controlledChildren) do OptionsPrivate.displayButtons[childId]:SetGroup(newid) end end OptionsPrivate.StopGrouping() OptionsPrivate.SortDisplayButtons(nil, true) frame:OnRename(uid, oldid, newid) WeakAuras.PickDisplay(newid) local parent = data.parent while parent do OptionsPrivate.ClearOptions(parent) local parentData = WeakAuras.GetData(parent) parent = parentData.parent end end local function OptionsFrame() if(frame) then return frame else return nil end end if not WeakAuras.ToggleOptions then ---@type fun(msg: string, Private: Private) function WeakAuras.ToggleOptions(msg, Private) if not Private then return end if not OptionsPrivate.Private then OptionsPrivate.Private = Private Private.OptionsFrame = OptionsFrame for _, fn in ipairs(OptionsPrivate.registerRegions) do fn() end OptionsPrivate.Private.callbacks:RegisterCallback("AuraWarningsUpdated", function(event, uid) local id = OptionsPrivate.Private.UIDtoID(uid) if displayButtons[id] then -- The button does not yet exists if a new aura is created displayButtons[id]:UpdateWarning() end local data = Private.GetDataByUID(uid) if data and data.parent then local button = OptionsPrivate.GetDisplayButton(data.parent); if button then button:UpdateParentWarning() end end end) OptionsPrivate.Private.callbacks:RegisterCallback("ScanForLoads", AfterScanForLoads) OptionsPrivate.Private.callbacks:RegisterCallback("AboutToDelete", OnAboutToDelete) OptionsPrivate.Private.callbacks:RegisterCallback("Rename", OnRename) OptionsPrivate.Private.OpenUpdate = OptionsPrivate.OpenUpdate end if(frame and frame:IsVisible()) then WeakAuras.HideOptions(); elseif (InCombatLockdown()) then WeakAuras.prettyPrint(L["Options will open after combat ends."]) reopenAfterCombat = true; else WeakAuras.ShowOptions(msg); end end end function WeakAuras.HideOptions() if(frame) then frame:Hide() end end function WeakAuras.IsOptionsOpen() if(frame and frame:IsVisible()) then return true; else return false; end end local function EnsureDisplayButton(data) local id = data.id; if not(displayButtons[id]) then displayButtons[id] = AceGUI:Create("WeakAurasDisplayButton"); if(displayButtons[id]) then displayButtons[id]:SetData(data); displayButtons[id]:Initialize(); displayButtons[id]:UpdateWarning() else print("|cFF8800FFWeakAuras|r: Error creating button for", id); end end end local function GetSortedOptionsLists() local loadedSorted, unloadedSorted = {}, {}; local to_sort = {}; for id, data in pairs(db.displays) do if(data.parent) then -- Do nothing; children will be added later elseif(OptionsPrivate.Private.loaded[id]) then tinsert(to_sort, id); end end table.sort(to_sort, function(a, b) return a:lower() < b:lower() end) for _, id in ipairs(to_sort) do local data = WeakAuras.GetData(id); for child in OptionsPrivate.Private.TraverseAll(data) do tinsert(loadedSorted, child.id) end end wipe(to_sort); for id, data in pairs(db.displays) do if(data.parent) then -- Do nothing; children will be added later elseif not(OptionsPrivate.Private.loaded[id]) then tinsert(to_sort, id); end end table.sort(to_sort, function(a, b) return a:lower() < b:lower() end) for _, id in ipairs(to_sort) do local data = WeakAuras.GetData(id); for child in OptionsPrivate.Private.TraverseAll(data) do tinsert(unloadedSorted, child.id) end end return loadedSorted, unloadedSorted; end local function LayoutDisplayButtons(msg) local total = 0; for _,_ in pairs(db.displays) do total = total + 1; end local loadedSorted, unloadedSorted = GetSortedOptionsLists(); frame:SetLoadProgressVisible(true) if OptionsPrivate.Private.CompanionData.slugs then frame.buttonsScroll:AddChild(frame.pendingInstallButton); frame.buttonsScroll:AddChild(frame.pendingUpdateButton); end frame.buttonsScroll:AddChild(frame.loadedButton); frame.buttonsScroll:AddChild(frame.unloadedButton); local func2 = function() local num = frame.loadProgressNum or 0; for _, id in pairs(unloadedSorted) do local data = WeakAuras.GetData(id); if(data) then EnsureDisplayButton(data); WeakAuras.UpdateThumbnail(data); frame.buttonsScroll:AddChild(displayButtons[data.id]); if (num % 50 == 0) then frame.buttonsScroll:ResumeLayout() frame.buttonsScroll:PerformLayout() frame.buttonsScroll:PauseLayout() end num = num + 1; end frame.loadProgress:SetText(L["Creating buttons: "]..num.."/"..total); frame.loadProgressNum = num; coroutine.yield(); end frame.buttonsScroll:ResumeLayout() frame.buttonsScroll:PerformLayout() OptionsPrivate.SortDisplayButtons(msg); local suspended = OptionsPrivate.Private.PauseAllDynamicGroups() if (WeakAuras.IsOptionsOpen()) then for id, button in pairs(displayButtons) do if OptionsPrivate.Private.loaded[id] then button:PriorityShow(1); coroutine.yield() end end OptionsPrivate.Private.OptionsFrame().loadedButton:RecheckVisibility() coroutine.yield() end OptionsPrivate.Private.ResumeAllDynamicGroups(suspended) frame:SetLoadProgressVisible(false) end local func1 = function() local num = frame.loadProgressNum or 0; frame.buttonsScroll:PauseLayout() for _, id in pairs(loadedSorted) do local data = WeakAuras.GetData(id); if(data) then EnsureDisplayButton(data); WeakAuras.UpdateThumbnail(data); local button = displayButtons[data.id] frame.buttonsScroll:AddChild(button); num = num + 1; end if (num % 50 == 0) then frame.buttonsScroll:ResumeLayout() frame.buttonsScroll:PerformLayout() frame.buttonsScroll:PauseLayout() end frame.loadProgress:SetText(L["Creating buttons: "]..num.."/"..total); frame.loadProgressNum = num; coroutine.yield(); end local co2 = coroutine.create(func2); OptionsPrivate.Private.Threads:Add("LayoutDisplayButtons2", co2); end local co1 = coroutine.create(func1); OptionsPrivate.Private.Threads:Add("LayoutDisplayButtons1", co1); end function OptionsPrivate.DeleteAuras(auras, parents) local func1 = function() frame:SetLoadProgressVisible(true) local num = 0 local total = 0 for _, auraData in pairs(auras) do total = total +1 end frame.loadProgress:SetText(L["Deleting auras: "]..num.."/"..total) local suspended = OptionsPrivate.Private.PauseAllDynamicGroups() OptionsPrivate.massDelete = true for _, auraData in pairs(auras) do WeakAuras.Delete(auraData) num = num +1 frame.loadProgress:SetText(L["Deleting auras: "]..num.."/"..total) coroutine.yield() end OptionsPrivate.massDelete = false if parents then for id in pairs(parents) do local parentData = WeakAuras.GetData(id) local parentButton = OptionsPrivate.GetDisplayButton(id) WeakAuras.UpdateGroupOrders(parentData) if(#parentData.controlledChildren == 0) then parentButton:DisableExpand() else parentButton:EnableExpand() end parentButton:SetNormalTooltip() WeakAuras.Add(parentData) WeakAuras.ClearAndUpdateOptions(parentData.id) parentButton:UpdateParentWarning() frame.loadProgress:SetText(L["Finishing..."]) coroutine.yield() end end OptionsPrivate.Private.ResumeAllDynamicGroups(suspended) OptionsPrivate.SortDisplayButtons(nil, true) frame:SetLoadProgressVisible(false) end local co1 = coroutine.create(func1) OptionsPrivate.Private.Threads:Add("Deleting Auras", co1) end function WeakAuras.ShowOptions(msg) local firstLoad = not(frame); OptionsPrivate.Private.Pause(); OptionsPrivate.Private.SetFakeStates() WeakAuras.spellCache.Build() if (firstLoad) then frame = OptionsPrivate.CreateFrame(); frame.buttonsScroll.frame:Show(); LayoutDisplayButtons(msg); end if (frame:GetWidth() > GetScreenWidth()) then frame:SetWidth(GetScreenWidth()) end if (frame:GetHeight() > GetScreenHeight() - 50) then frame:SetHeight(GetScreenHeight() - 50) end frame.buttonsScroll.frame:Show(); if (frame.needsSort) then OptionsPrivate.SortDisplayButtons(); frame.needsSort = nil; end frame:Show(); if (OptionsPrivate.Private.mouseFrame) then OptionsPrivate.Private.mouseFrame:OptionsOpened(); end if (OptionsPrivate.Private.personalRessourceDisplayFrame) then OptionsPrivate.Private.personalRessourceDisplayFrame:OptionsOpened(); end if frame.moversizer then frame.moversizer:OptionsOpened() end if not(firstLoad) then -- Show what was last shown local suspended = OptionsPrivate.Private.PauseAllDynamicGroups() for id, button in pairs(displayButtons) do button:SyncVisibility() end OptionsPrivate.Private.ResumeAllDynamicGroups(suspended) end if (frame.pickedDisplay) then if (OptionsPrivate.IsPickedMultiple()) then local children = {} for k,v in pairs(tempGroup.controlledChildren) do children[k] = v end frame:PickDisplayBatch(children); else WeakAuras.PickDisplay(frame.pickedDisplay); end else frame:NewAura(); end if (frame.window == "codereview") then local codereview = OptionsPrivate.CodeReview(frame, true) if codereview then codereview:Close(); end end if firstLoad then frame:ShowTip() end end function OptionsPrivate.UpdateOptions() frame:UpdateOptions() end function WeakAuras.ClearAndUpdateOptions(id, clearChildren) frame:ClearAndUpdateOptions(id, clearChildren) end function OptionsPrivate.ClearOptions(id) frame:ClearOptions(id) end function WeakAuras.FillOptions() frame:FillOptions() end function OptionsPrivate.EnsureOptions(data, subOption) return frame:EnsureOptions(data, subOption) end function OptionsPrivate.GetPickedDisplay() return frame:GetPickedDisplay() end function OptionsPrivate.OpenTextEditor(...) OptionsPrivate.TextEditor(frame):Open(...); end function OptionsPrivate.ExportToString(id) OptionsPrivate.ImportExport(frame):Open("export", id); end function OptionsPrivate.ExportToTable(id) OptionsPrivate.ImportExport(frame):Open("table", id); end function OptionsPrivate.ImportFromString() OptionsPrivate.ImportExport(frame):Open("import"); end function OptionsPrivate.OpenDebugLog(text) OptionsPrivate.DebugLog(frame):Open(text) end function OptionsPrivate.OpenUpdate(data, children, target, linkedAuras, sender, callbackFunc) return OptionsPrivate.UpdateFrame(frame):Open(data, children, target, linkedAuras, sender, callbackFunc) end function OptionsPrivate.ConvertDisplay(data, newType) local id = data.id; local visibility = displayButtons[id]:GetVisibility(); displayButtons[id]:PriorityHide(2); if OptionsPrivate.Private.regions[id] and OptionsPrivate.Private.regions[id].region then OptionsPrivate.Private.regions[id].region:Collapse() end OptionsPrivate.Private.CollapseAllClones(id); OptionsPrivate.Private.Convert(data, newType); displayButtons[id]:Initialize(); displayButtons[id]:PriorityShow(visibility); frame:ClearOptions(id) frame:FillOptions(); WeakAuras.UpdateThumbnail(data); WeakAuras.SetMoverSizer(id) OptionsPrivate.ResetMoverSizer(); OptionsPrivate.SortDisplayButtons() end function WeakAuras.NewDisplayButton(data, massEdit) local id = data.id; OptionsPrivate.Private.ScanForLoads({[id] = true}); EnsureDisplayButton(db.displays[id]); WeakAuras.UpdateThumbnail(db.displays[id]); frame.buttonsScroll:AddChild(displayButtons[id]); if not massEdit then OptionsPrivate.SortDisplayButtons() end end function WeakAuras.UpdateGroupOrders(data) if(data.controlledChildren) then local total = #data.controlledChildren; for index, id in pairs(data.controlledChildren) do local button = OptionsPrivate.GetDisplayButton(id); button:SetGroupOrder(index, total); end end end function OptionsPrivate.UpdateButtonsScroll() if OptionsPrivate.Private.IsOptionsProcessingPaused() then return end frame.buttonsScroll:DoLayout() end local function addButton(button, aurasMatchingFilter, visible) button.frame:Show(); if button.AcquireThumbnail then button:AcquireThumbnail() end tinsert(frame.buttonsScroll.children, button); visible[button] = true if button.data.controlledChildren and button:GetExpanded() then for _, childId in ipairs(button.data.controlledChildren) do if aurasMatchingFilter[childId] then addButton(displayButtons[childId], aurasMatchingFilter, visible) end end end end local previousFilter; local pendingUpdateButtons = {} local pendingInstallButtons = {} function OptionsPrivate.SortDisplayButtons(filter, overrideReset, id) if (OptionsPrivate.Private.IsOptionsProcessingPaused()) then return; end local recenter = false; filter = filter or (overrideReset and previousFilter or ""); if(frame.filterInput:GetText() ~= filter) then frame.filterInput:SetText(filter); end if(previousFilter and previousFilter ~= "" and (filter == "" or not filter)) then recenter = true; end previousFilter = filter; filter = filter:lower(); wipe(frame.buttonsScroll.children); local pendingInstallButtonShown = false if OptionsPrivate.Private.CompanionData.stash then for id, companionData in pairs(OptionsPrivate.Private.CompanionData.stash) do if not pendingInstallButtonShown then tinsert(frame.buttonsScroll.children, frame.pendingInstallButton) pendingInstallButtonShown = true end local child = pendingInstallButtons[id] if frame.pendingInstallButton:GetExpanded() then if not child then child = AceGUI:Create("WeakAurasPendingInstallButton") pendingInstallButtons[id] = child child:Initialize(id, companionData) if companionData.logo then child:SetLogo(companionData.logo) end if companionData.refreshLogo then child:SetRefreshLogo(companionData.refreshLogo) end child.frame:Show() child:AcquireThumbnail() frame.buttonsScroll:AddChild(child) else if not child.frame:IsShown() then child.frame:Show() child:AcquireThumbnail() end tinsert(frame.buttonsScroll.children, child) end elseif child then child.frame:Hide() if child.ReleaseThumbnail then child:ReleaseThumbnail() end end end end if not pendingInstallButtonShown and frame.pendingInstallButton then frame.pendingInstallButton.frame:Hide() end local pendingUpdateButtonShown = false if OptionsPrivate.Private.CompanionData.slugs then local buttonsShown = {} for _, button in pairs(pendingUpdateButtons) do button:ResetLinkedAuras() end for id, aura in pairs(WeakAurasSaved.displays) do if not aura.ignoreWagoUpdate and aura.url and aura.url ~= "" then local slug, version = aura.url:match("wago.io/([^/]+)/([0-9]+)") if not slug and not version then slug = aura.url:match("wago.io/([^/]+)$") version = 1 end if slug and version then local auraData = OptionsPrivate.Private.CompanionData.slugs[slug] if auraData and auraData.wagoVersion then if tonumber(auraData.wagoVersion) > tonumber(version) then -- there is an update for this aura if not pendingUpdateButtonShown then tinsert(frame.buttonsScroll.children, frame.pendingUpdateButton) pendingUpdateButtonShown = true end if frame.pendingUpdateButton:GetExpanded() then local child = pendingUpdateButtons[slug] if not child then child = AceGUI:Create("WeakAurasPendingUpdateButton") pendingUpdateButtons[slug] = child child:Initialize(slug, auraData) if auraData.logo then child:SetLogo(auraData.logo) end if auraData.refreshLogo then child:SetRefreshLogo(auraData.refreshLogo) end child.frame:Show() child:AcquireThumbnail() frame.buttonsScroll:AddChild(child) buttonsShown[slug] = true end if not child.frame:IsShown() then child.frame:Show() child:AcquireThumbnail() end if not buttonsShown[slug] then tinsert(frame.buttonsScroll.children, child) buttonsShown[slug] = true end child:MarkLinkedAura(id) for childData in OptionsPrivate.Private.TraverseAllChildren(aura) do child:MarkLinkedChildren(childData.id) end end end end end end end -- hide all buttons not marked as shown for slug, button in pairs(pendingUpdateButtons) do if not buttonsShown[slug] then if button and button.frame:IsShown() then button.frame:Hide() if button.ReleaseThumbnail then button:ReleaseThumbnail() end end end end end if not pendingUpdateButtonShown and frame.pendingUpdateButton then frame.pendingUpdateButton.frame:Hide() end tinsert(frame.buttonsScroll.children, frame.loadedButton); local aurasMatchingFilter = {} local useTextFilter = filter ~= "" local filterTable = OptionsPrivate.Private.splitAtOr(filter) local topLevelLoadedAuras = {} local topLevelUnloadedAuras = {} local visible = {} for id, child in pairs(displayButtons) do if child.data.controlledChildren then local hasLoaded, hasStandBy, hasNotLoaded = 0, 0, 0 for leaf in OptionsPrivate.Private.TraverseLeafs(child.data) do local id = leaf.id if OptionsPrivate.Private.loaded[id] == true then hasLoaded = hasLoaded + 1 elseif OptionsPrivate.Private.loaded[id] == false then hasStandBy = hasStandBy + 1 else hasNotLoaded = hasNotLoaded + 1 end end if hasLoaded > 0 then child:SetLoaded(1, "loaded", L["Loaded"], L["%d displays loaded"]:format(hasLoaded)) elseif hasStandBy > 0 then child:SetLoaded(2, "standby", L["Standby"], L["%d displays on standby"]:format(hasStandBy)) elseif hasNotLoaded > 0 then child:SetLoaded(3, "unloaded", L["Not Loaded"], L["%d displays not loaded"]:format(hasNotLoaded)) else child:ClearLoaded() end else if OptionsPrivate.Private.loaded[id] == true then child:SetLoaded(1, "loaded", L["Loaded"], L["This display is currently loaded"]) elseif OptionsPrivate.Private.loaded[id] == false then child:SetLoaded(2, "standby", L["Standby"], L["This display is on standby, it will be loaded when needed."]) else child:SetLoaded(3, "unloaded", L["Not Loaded"], L["This display is not currently loaded"]) end end if useTextFilter then for _, word in ipairs(filterTable) do if(id:lower():find(word, 1, true)) then aurasMatchingFilter[id] = true for parent in OptionsPrivate.Private.TraverseParents(child.data) do aurasMatchingFilter[parent.id] = true end end end else aurasMatchingFilter[id] = true end if not child:GetGroup() then -- Top Level aura if OptionsPrivate.Private.loaded[id] ~= nil then tinsert(topLevelLoadedAuras, id) else tinsert(topLevelUnloadedAuras, id) end end end wipe(frame.loadedButton.childButtons) if frame.loadedButton:GetExpanded() then table.sort(topLevelLoadedAuras, function(a, b) return a:lower() < b:lower() end) for _, id in ipairs(topLevelLoadedAuras) do if aurasMatchingFilter[id] then addButton(displayButtons[id], aurasMatchingFilter, visible) end end end for _, id in ipairs(topLevelLoadedAuras) do for child in OptionsPrivate.Private.TraverseLeafsOrAura(WeakAuras.GetData(id)) do tinsert(frame.loadedButton.childButtons, displayButtons[child.id]) end end tinsert(frame.buttonsScroll.children, frame.unloadedButton); wipe(frame.unloadedButton.childButtons) if frame.unloadedButton:GetExpanded() then table.sort(topLevelUnloadedAuras, function(a, b) return a:lower() < b:lower() end) for _, id in ipairs(topLevelUnloadedAuras) do if aurasMatchingFilter[id] then addButton(displayButtons[id], aurasMatchingFilter, visible) end end end for _, id in ipairs(topLevelUnloadedAuras) do for child in OptionsPrivate.Private.TraverseLeafsOrAura(WeakAuras.GetData(id)) do tinsert(frame.unloadedButton.childButtons, displayButtons[child.id]) end end for _, child in pairs(displayButtons) do if(not visible[child]) then child.frame:Hide(); if child.ReleaseThumbnail then child:ReleaseThumbnail() end end end frame.buttonsScroll:DoLayout(); if(recenter) then frame:CenterOnPicked(); end end function OptionsPrivate.IsPickedMultiple() if(frame.pickedDisplay == tempGroup) then return true; else return false; end end function OptionsPrivate.IsDisplayPicked(id) if(frame.pickedDisplay == tempGroup) then for child in OptionsPrivate.Private.TraverseLeafs(tempGroup) do if(id == child.id) then return true; end end return false; else return frame.pickedDisplay == id; end end function WeakAuras.PickDisplay(id, tab, noHide) frame:PickDisplay(id, tab, noHide) OptionsPrivate.UpdateButtonsScroll() end function OptionsPrivate.PickAndEditDisplay(id) frame:PickDisplay(id); OptionsPrivate.UpdateButtonsScroll() displayButtons[id].callbacks.OnRenameClick(); end function OptionsPrivate.ClearPick(id) frame:ClearPick(id); end function OptionsPrivate.ClearPicks() frame:ClearPicks(); end function OptionsPrivate.PickDisplayMultiple(id) frame:PickDisplayMultiple(id); end function OptionsPrivate.PickDisplayMultipleShift(target) if (frame.pickedDisplay) then -- get first aura selected local first; if (OptionsPrivate.IsPickedMultiple()) then first = tempGroup.controlledChildren[#tempGroup.controlledChildren]; else first = frame.pickedDisplay; end if (first and first ~= target) then -- check if target and first are in same group and are not a group local firstData = WeakAuras.GetData(first); local targetData = WeakAuras.GetData(target); if (firstData.parent == targetData.parent and not targetData.controlledChildren and not firstData.controlledChildren) then local batchSelection = {}; -- in a group if (firstData.parent) then local group = WeakAuras.GetData(targetData.parent); for index, child in ipairs(group.controlledChildren) do -- 1st button if (child == target or child == first) then table.insert(batchSelection, child); for i = index + 1, #group.controlledChildren do local current = group.controlledChildren[i]; if (WeakAuras.GetData(current).controlledChildren) then -- Skip sub groups else table.insert(batchSelection, current); end -- last button: stop selection if (current == target or current == first) then break; end end break; end end elseif (firstData.parent == nil and targetData.parent == nil) then -- top-level for index, button in ipairs(frame.buttonsScroll.children) do if button.type == "WeakAurasDisplayButton" then local data = button.data; -- 1st button if (data and (data.id == target or data.id == first)) then table.insert(batchSelection, data.id); for i = index + 1, #frame.buttonsScroll.children do local current = frame.buttonsScroll.children[i]; local currentData = current.data; if currentData and not currentData.parent and not currentData.controlledChildren then table.insert(batchSelection, currentData.id); -- last button: stop selection if (currentData.id == target or currentData.id == first) then break; end end end break; end end end end if #batchSelection > 0 then frame:PickDisplayBatch(batchSelection); end end end else WeakAuras.PickDisplay(target); end end function OptionsPrivate.GetDisplayButton(id) if(id and displayButtons[id]) then return displayButtons[id]; end end function OptionsPrivate.AddDisplayButton(data) EnsureDisplayButton(data); WeakAuras.UpdateThumbnail(data); frame.buttonsScroll:AddChild(displayButtons[data.id]); end function OptionsPrivate.StartGrouping(data) if not data then return end if not OptionsPrivate.IsDisplayPicked(data) then WeakAuras.PickDisplay(data.id) end if (frame.pickedDisplay == tempGroup and #tempGroup.controlledChildren > 0) then local children = {}; -- start grouping for selected buttons for index, childId in ipairs(tempGroup.controlledChildren) do local button = OptionsPrivate.GetDisplayButton(childId); button:StartGrouping(tempGroup.controlledChildren, true); children[childId] = true; end -- set grouping for non selected buttons for _, button in pairs(displayButtons) do if not children[button.data.id] then button:StartGrouping(tempGroup.controlledChildren, false); end end else local children = {}; for child in OptionsPrivate.Private.TraverseAllChildren(data) do children[child.id] = true end for id, button in pairs(displayButtons) do button:StartGrouping({data.id}, data.id == id, data.regionType == "dynamicgroup" or data.regionType == "group", children[id]); end end end function OptionsPrivate.StopGrouping(data) for id, button in pairs(displayButtons) do button:StopGrouping(); end end function OptionsPrivate.Ungroup(data) if not OptionsPrivate.IsDisplayPicked(data.id) then WeakAuras.PickDisplay(data.id) end if (frame.pickedDisplay == tempGroup and #tempGroup.controlledChildren > 0) then for index, childId in ipairs(tempGroup.controlledChildren) do local button = OptionsPrivate.GetDisplayButton(childId); button:Ungroup(data); end else local button = OptionsPrivate.GetDisplayButton(data.id); button:Ungroup(data); end WeakAuras.FillOptions() end function OptionsPrivate.DragReset() for _, button in pairs(displayButtons) do button:DragReset(); end OptionsPrivate.UpdateButtonsScroll() end local function CompareButtonOrder(a, b) if (a.data.parent == b.data.parent) then if (a.data.parent) then return a:GetGroupOrder() < b:GetGroupOrder() else return a.data.id < b.data.id end end -- Different parents, so find common parent by first -- going up a's hierarchy local parents = {} local aNode = a.data.id local lastAParent = aNode while(aNode) do local parent = WeakAuras.GetData(aNode).parent if (parent) then parents[parent] = aNode lastAParent = parent end aNode = parent end local bNode = b.data.id local lastBParent = bNode while(bNode) do local parent = WeakAuras.GetData(bNode).parent if parent then if (parents[parent]) then -- We have found the common parent, the last node in the chain is -- Compare the previous nodes GroupOrder local aButton = OptionsPrivate.GetDisplayButton(parents[parent]) local bButton = OptionsPrivate.GetDisplayButton(bNode) return aButton:GetGroupOrder() < bButton:GetGroupOrder() end lastBParent = parent end bNode = parent end -- If we are here there was no common parent local aButton = OptionsPrivate.GetDisplayButton(lastAParent) local bButton = OptionsPrivate.GetDisplayButton(lastBParent) return aButton.data.id < bButton.data.id end local function CompareButtonOrderReverse(a, b) return CompareButtonOrder(b, a) end function OptionsPrivate.Drop(mainAura, target, action, area) WeakAuras_DropDownMenu:Hide() local func1 = function() frame:SetLoadProgressVisible(true) local total = 0 local num = 0 for id, button in pairs(displayButtons) do if button:IsDragging() then total = total + 1 end end frame.loadProgress:SetText(L["Moving auras: "]..num.."/"..total) local mode = "" if (frame.pickedDisplay == tempGroup and #tempGroup.controlledChildren > 0) then mode = "MULTI" elseif mainAura.controlledChildren then mode = "GROUP" else mode = "SINGLE" end local buttonsToSort = {} for id, button in pairs(displayButtons) do if button:IsDragging() then tinsert(buttonsToSort, button) num = num + 1 frame.loadProgress:SetText(L["Preparing auras: "]..num.."/"..total) else button:Drop(mode, mainAura, target, action); end coroutine.yield() end num = 0 frame.loadProgress:SetText(L["Moving auras: "]..num.."/"..total) if mode == "MULTI" then -- If we are dragging and dropping multiple auras at once, the order in which we drop is important -- We want to preserve the top-down order -- Depending on how exactly we find the insert position, we need to use the right order of insertions if area == "GROUP" then table.sort(buttonsToSort, CompareButtonOrderReverse) elseif area == "BEFORE" then table.sort(buttonsToSort, CompareButtonOrder) else -- After table.sort(buttonsToSort, CompareButtonOrderReverse) end end for _, button in ipairs(buttonsToSort) do button:Drop(mode, mainAura, target, action) num = num + 1 frame.loadProgress:SetText(L["Moving auras: "]..num.."/"..total) coroutine.yield() end -- Update offset, this is a bit wasteful to do for every aura -- But we also need to update the offset if a parent was dragged for _, button in pairs(displayButtons) do button:UpdateOffset(); end coroutine.yield() frame:SetLoadProgressVisible(false) OptionsPrivate.SortDisplayButtons() OptionsPrivate.UpdateButtonsScroll() WeakAuras.FillOptions() end local co1 = coroutine.create(func1) OptionsPrivate.Private.Threads:Add("Dropping Auras", co1) end function OptionsPrivate.StartDrag(mainAura) WeakAuras_DropDownMenu:Hide() if (frame.pickedDisplay == tempGroup and #tempGroup.controlledChildren > 0) then -- Multi selection local children = {}; local size = #tempGroup.controlledChildren; -- set dragging for selected buttons in reverse for ordering for child in OptionsPrivate.Private.TraverseAllChildren(tempGroup) do local button = OptionsPrivate.GetDisplayButton(child.id); button:DragStart("MULTI", true, mainAura, size) children[child.id] = true end -- set dragging for non selected buttons for id, button in pairs(displayButtons) do if not children[button.data.id] then button:DragStart("MULTI", false, mainAura); end end else if mainAura.controlledChildren then -- Group aura local mode = "GROUP" local children = {}; for child in OptionsPrivate.Private.TraverseAll(mainAura) do local button = OptionsPrivate.GetDisplayButton(child.id); button:DragStart(mode, true, mainAura) children[child.id] = true end -- set dragging for non selected buttons for _, button in pairs(displayButtons) do if not children[button.data.id] then button:DragStart(mode, false, mainAura); end end else for id, button in pairs(displayButtons) do button:DragStart("SINGLE", id == mainAura.id, mainAura); end end end OptionsPrivate.UpdateButtonsScroll() end function OptionsPrivate.DropIndicator() local indicator = frame.dropIndicator if not indicator then ---@class Frame indicator = CreateFrame("Frame", "WeakAuras_DropIndicator") indicator:SetHeight(4) indicator:SetFrameStrata("FULLSCREEN") local groupTexture = indicator:CreateTexture(nil, "ARTWORK") groupTexture:SetBlendMode("ADD") groupTexture:SetTexture("Interface\\AddOns\\WeakAuras\\Media\\Textures\\Square_FullWhite") local lineTexture = indicator:CreateTexture(nil, "ARTWORK") lineTexture:SetBlendMode("ADD") lineTexture:SetAllPoints(indicator) lineTexture:SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-Tab-Highlight") indicator.lineTexture = lineTexture indicator.groupTexture = groupTexture frame.dropIndicator = indicator indicator:Hide() function indicator:ShowAction(target, action) self:Show() self:ClearAllPoints() if action == "GROUP" then self.groupTexture:ClearAllPoints() self.groupTexture:SetVertexColor(0.4, 0.7, 1, 0.7) self.groupTexture:Show() self.groupTexture:SetPoint("TOPLEFT", target.icon, "TOPRIGHT", 2, -1) self.groupTexture:SetPoint("BOTTOMRIGHT", target.frame, "BOTTOMRIGHT", 0, 1) else self.groupTexture:Hide() end -- Position line texture, if needed if action == "BEFORE" then self.lineTexture:Show() self:SetPoint("BOTTOMLEFT", target.frame, "TOPLEFT", 0, -1) self:SetPoint("BOTTOMRIGHT", target.frame, "TOPRIGHT", 0, -1) self:SetHeight(4) elseif action == "AFTER" then self.lineTexture:Show() self:SetPoint("TOPLEFT", target.frame, "BOTTOMLEFT", 0, 1) self:SetPoint("TOPRIGHT", target.frame, "BOTTOMRIGHT", 0, 1) self:SetHeight(4) else self.lineTexture:Hide() end end end return indicator end function WeakAuras.UpdateThumbnail(data) local id = data.id local button = displayButtons[id] if (not button) then return end button:UpdateThumbnail() end function OptionsPrivate.OpenTexturePicker(baseObject, paths, properties, textures, SetTextureFunc, adjustSize) OptionsPrivate.TexturePicker(frame):Open(baseObject, paths, properties, textures, SetTextureFunc, adjustSize) end function OptionsPrivate.OpenIconPicker(baseObject, paths, groupIcon) OptionsPrivate.IconPicker(frame):Open(baseObject, paths, groupIcon) end function OptionsPrivate.OpenModelPicker(baseObject, path) if not(C_AddOns.IsAddOnLoaded("WeakAurasModelPaths")) then local loaded, reason = C_AddOns.LoadAddOn("WeakAurasModelPaths"); if not(loaded) then reason = string.lower("|cffff2020" .. _G["ADDON_" .. reason] .. "|r.") WeakAuras.prettyPrint(string.format(L["ModelPaths could not be loaded, the addon is %s"], reason)); WeakAuras.ModelPaths = {}; end OptionsPrivate.ModelPicker(frame).modelTree:SetTree(WeakAuras.ModelPaths) end OptionsPrivate.ModelPicker(frame):Open(baseObject, path); end function OptionsPrivate.OpenCodeReview(data) OptionsPrivate.CodeReview(frame):Open(data); end function OptionsPrivate.OpenTriggerTemplate(data, targetId) if not(C_AddOns.IsAddOnLoaded("WeakAurasTemplates")) then local loaded, reason = C_AddOns.LoadAddOn("WeakAurasTemplates"); if not(loaded) then reason = string.lower("|cffff2020" .. _G["ADDON_" .. reason] .. "|r.") WeakAuras.prettyPrint(string.format(L["Templates could not be loaded, the addon is %s"], reason)); return; end frame.newView = WeakAuras.CreateTemplateView(OptionsPrivate.Private, frame); end -- This is called multiple times if a group is selected if frame.window ~= "newView" then frame.newView:Open(data, targetId); end end OptionsPrivate.currentDynamicTextInput = false; local BaseDynamicTextCodes = { trigger = { {type = "mini", name = "p", desc = L["Progress - The remaining time of a timer, or a non-timer value"]}, {type = "mini", name = "t", desc = L["Total - The maximum duration of a timer, or a maximum non-timer value"]}, {type = "mini", name = "n", desc = L["Name - The name of the display (usually an aura name), or the display's ID if there is no dynamic name"]}, {type = "mini", name = "i", desc = L["Icon - The icon associated with the display"]}, {type = "mini", name = "s", desc = L["Stacks - The number of stacks of an aura (usually)"]}, }, global = { {type = "mini", name = "c", desc = L["Custom - Allows you to define a custom Lua function that returns a list of string values. %c1 will be replaced by the first value returned, %c2 by the second, etc."]}, {type = "mini", name = "%", desc = L["% - To show a percent sign"]}, } } function OptionsPrivate.UpdateTextReplacements(frame, data) frame.scrollList:ReleaseChildren() local props = OptionsPrivate.Private.GetAdditionalProperties(data) local sortedProps = {} -- Add global header and markers table.insert(sortedProps, {type = "header", triggerNum = 0, name = "Global Properties"}) for index, icon in ipairs(ICON_LIST) do table.insert(sortedProps, {type = "marker", triggerNum = 0, name = "{rt"..index.."}", desc = icon..":0|t", widthFraction = #ICON_LIST}) end -- Add base dynamic text codes local globalProps = {} tAppendAll(globalProps, CopyTable(BaseDynamicTextCodes.trigger)) tAppendAll(globalProps, CopyTable(BaseDynamicTextCodes.global)) for _, prop in ipairs(globalProps) do prop.widthFraction = #globalProps prop.triggerNum = 0 table.insert(sortedProps, prop) end -- Process each trigger's properties for triggerNum, triggerProps in pairs(props) do if next(triggerProps) then -- Create a temporary table for this trigger's properties local tempProps = {} -- Add the properties to the temporary table for name, data in pairs(triggerProps) do table.insert(tempProps, {triggerNum = triggerNum, name = name, desc = data.display}) end -- Sort the temporary table by name table.sort(tempProps, function(a, b) return a.name < b.name end) -- Add a header for the trigger table.insert(sortedProps, {type = "header", triggerNum = triggerNum, name = OptionsPrivate.GetTriggerTitle(data, triggerNum)}) -- Add the base properties for the trigger for _, v in ipairs(BaseDynamicTextCodes.trigger) do local prop = CopyTable(v) prop.widthFraction = #BaseDynamicTextCodes.trigger prop.triggerNum = triggerNum table.insert(sortedProps, prop) end -- Add the sorted properties to the sortedProps table for _, prop in ipairs(tempProps) do table.insert(sortedProps, prop) end end end -- Create a modified WeakAurasSnippetButton for each property and add it to ScrollList local lastType, miniGroup for i, prop in ipairs(sortedProps) do if prop.type == "header" then local heading = AceGUI:Create("Heading") heading:SetText(prop.name) heading:SetRelativeWidth(1) heading.label:SetFontObject(GameFontNormalSmall2) frame.scrollList:AddChild(heading) else if ((prop.type == "mini" or prop.type == "marker") and prop.type ~= lastType) then miniGroup = AceGUI:Create("SimpleGroup") miniGroup:SetLayout("Flow") miniGroup:SetAutoAdjustHeight(true) miniGroup:SetRelativeWidth(1) frame.scrollList:AddChild(miniGroup) end local button = AceGUI:Create("WeakAurasSnippetButton") local propIndex = prop.triggerNum > 0 and ("%s"):format(prop.triggerNum) or "" local propPrefix = prop.triggerNum > 0 and ("%%%s."):format(propIndex) or "%" if prop.type == "marker" then button:SetTitle(prop.desc) else button:SetTitle(string.format("|cFFFFCC00%s|r%s", propPrefix, prop.name)) end if prop.type == "mini" or prop.type == "marker" then button:SetRelativeWidth((1/prop.widthFraction) - 1e-10) else button:SetRelativeWidth(1) end button.title:SetFontObject(GameFontNormal) button.frame:SetHeight(28) button:SetDynamicTextStyle() -- Set Tooltip if prop.type ~= "marker" then button.frame:SetScript("OnEnter", function(frame) local tooltip = GameTooltip tooltip:SetWidth(300) tooltip:SetOwner(frame, "ANCHOR_RIGHT") tooltip:ClearLines() tooltip:AddLine(("%s%s"):format(propPrefix, prop.name)) tooltip:AddLine(prop.desc, 1, 1, 1, true) if prop.name ~= "c" and prop.name ~= "%" then tooltip:AddLine("\n") tooltip:AddLine( prop.triggerNum > 0 and L["The trigger number is optional. When no trigger number is specified, the trigger selected via dynamic information will be used."] or L["By default this shows the information from the trigger selected via dynamic information. The information from a specific trigger can be shown via e.g. %2.p."], 0.8, 0.8, 0.8, true) end tooltip:Show() frame.obj:Fire("OnEnter") end) else button.frame:SetScript("OnEnter", nil) end -- Insert dynamic text property on click button:SetCallback("OnClick", function() local insertProp if prop.type == "marker" then insertProp = prop.name else if IsShiftKeyDown() then insertProp = prop.name == "%" and "%%" or ("%%{%s}"):format(prop.name) if prop.triggerNum > 0 then insertProp = string.format("%%{%d.%s}", propIndex, prop.name) end else insertProp = prop.name == "%" and "%%" or ("%%%s"):format(prop.name) if prop.triggerNum > 0 then insertProp = string.format("%%%d.%s", propIndex, prop.name) end end end OptionsPrivate.currentDynamicTextInput.editbox:Insert(insertProp) OptionsPrivate.currentDynamicTextInput.editbox:SetFocus() end) if prop.type == "mini" or prop.type == "marker" then miniGroup:AddChild(button) else frame.scrollList:AddChild(button) end end lastType = prop.type end end function OptionsPrivate.ResetMoverSizer() if(frame and frame.mover and frame.moversizer and frame.mover.moving.region and frame.mover.moving.data) then frame.moversizer:SetToRegion(frame.mover.moving.region, frame.mover.moving.data); end end function WeakAuras.SetMoverSizer(id) OptionsPrivate.Private.EnsureRegion(id) if OptionsPrivate.Private.regions[id].region.toShow then frame.moversizer:SetToRegion(OptionsPrivate.Private.regions[id].region, db.displays[id]) else if OptionsPrivate.Private.clones[id] then local _, clone = next(OptionsPrivate.Private.clones[id]) if clone then frame.moversizer:SetToRegion(clone, db.displays[id]) end end end end function WeakAuras.GetMoverSizerId() return frame.moversizer:GetCurrentId() end local function AddDefaultSubRegions(data) data.subRegions = data.subRegions or {} for type, subRegionData in pairs(OptionsPrivate.Private.subRegionTypes) do if subRegionData.addDefaultsForNewAura then subRegionData.addDefaultsForNewAura(data) end end end function WeakAuras.NewAura(sourceData, regionType, targetId) local function ensure(t, k, v) return t and k and v and t[k] == v end local new_id = OptionsPrivate.Private.FindUnusedId("New") local data = {id = new_id, regionType = regionType, uid = WeakAuras.GenerateUniqueID()} WeakAuras.DeepMixin(data, OptionsPrivate.Private.data_stub); if (sourceData) then WeakAuras.DeepMixin(data, sourceData); end data.internalVersion = WeakAuras.InternalVersion(); OptionsPrivate.Private.validate(data, OptionsPrivate.Private.regionTypes[regionType].default); AddDefaultSubRegions(data) if targetId then local target = OptionsPrivate.GetDisplayButton(targetId); local group if (target) then if (target:IsGroup()) then group = target; else group = OptionsPrivate.GetDisplayButton(target.data.parent); end if (group) then -- Sanity check so that we don't create a group/dynamic group in a group if (regionType == "group" or regionType == "dynamicgroup") and group.data.regionType == "dynamicgroup" then return end local children = group.data.controlledChildren; local index = target:GetGroupOrder(); if (ensure(children, index, target.data.id)) then -- account for insert position index = index + 1; tinsert(children, index, data.id); else -- move source into group as the first child tinsert(children, 1, data.id); end data.parent = group.data.id; WeakAuras.Add(data); WeakAuras.Add(group.data); OptionsPrivate.Private.AddParents(group.data) WeakAuras.NewDisplayButton(data); WeakAuras.UpdateGroupOrders(group.data); OptionsPrivate.ClearOptions(group.data.id); group.callbacks.UpdateExpandButton(); group:UpdateParentWarning(); group:Expand(); group:ReloadTooltip(); OptionsPrivate.PickAndEditDisplay(data.id); else -- move source into the top-level list WeakAuras.Add(data); WeakAuras.NewDisplayButton(data); OptionsPrivate.PickAndEditDisplay(data.id); end else error(string.format("Calling 'WeakAuras.NewAura' with invalid groupId %s. Reload your UI to fix the display list.", targetId)) end else -- move source into the top-level list WeakAuras.Add(data); WeakAuras.NewDisplayButton(data); OptionsPrivate.PickAndEditDisplay(data.id); end end function OptionsPrivate.ResetCollapsed(id, namespace) if id then if namespace and collapsedOptions[id] then collapsedOptions[id][namespace] = nil else collapsedOptions[id] = nil end end end function OptionsPrivate.IsCollapsed(id, namespace, path, default) local tmp = collapsedOptions[id] if tmp == nil then return default end tmp = tmp[namespace] if tmp == nil then return default end if type(path) ~= "table" then tmp = tmp[path] else for _, key in ipairs(path) do tmp = tmp[key] if tmp == nil or tmp[collapsed] then break end end end if tmp == nil or tmp[collapsed] == nil then return default else return tmp[collapsed] end end function OptionsPrivate.SetCollapsed(id, namespace, path, v) collapsedOptions[id] = collapsedOptions[id] or {} collapsedOptions[id][namespace] = collapsedOptions[id][namespace] or {} if type(path) ~= "table" then collapsedOptions[id][namespace][path] = collapsedOptions[id][namespace][path] or {} collapsedOptions[id][namespace][path][collapsed] = v else local tmp = collapsedOptions[id][namespace] or {} for _, key in ipairs(path) do tmp[key] = tmp[key] or {} tmp = tmp[key] end tmp[collapsed] = v end end function OptionsPrivate.MoveCollapseDataUp(id, namespace, path) collapsedOptions[id] = collapsedOptions[id] or {} collapsedOptions[id][namespace] = collapsedOptions[id][namespace] or {} if type(path) ~= "table" then collapsedOptions[id][namespace][path], collapsedOptions[id][namespace][path - 1] = collapsedOptions[id][namespace][path - 1], collapsedOptions[id][namespace][path] else local tmp = collapsedOptions[id][namespace] local lastKey = tremove(path) for _, key in ipairs(path) do tmp[key] = tmp[key] or {} tmp = tmp[key] end tmp[lastKey], tmp[lastKey - 1] = tmp[lastKey - 1], tmp[lastKey] end end function OptionsPrivate.MoveCollapseDataDown(id, namespace, path) collapsedOptions[id] = collapsedOptions[id] or {} collapsedOptions[id][namespace] = collapsedOptions[id][namespace] or {} if type(path) ~= "table" then collapsedOptions[id][namespace][path], collapsedOptions[id][namespace][path + 1] = collapsedOptions[id][namespace][path + 1], collapsedOptions[id][namespace][path] else local tmp = collapsedOptions[id][namespace] local lastKey = tremove(path) for _, key in ipairs(path) do tmp[key] = tmp[key] or {} tmp = tmp[key] end tmp[lastKey], tmp[lastKey + 1] = tmp[lastKey + 1], tmp[lastKey] end end function OptionsPrivate.RemoveCollapsed(id, namespace, path) local data = collapsedOptions[id] and collapsedOptions[id][namespace] if not data then return end local index local maxIndex = 0 if type(path) ~= "table" then index = path else index = path[#path] for i = 1, #path - 1 do data = data[path[i]] if not data then return end end end for k in pairs(data) do if k ~= collapsed then maxIndex = max(maxIndex, k) end end while index <= maxIndex do data[index] = data[index + 1] index = index + 1 end end function OptionsPrivate.InsertCollapsed(id, namespace, path, value) local data = collapsedOptions[id] and collapsedOptions[id][namespace] if not data then return end local insertPoint local maxIndex if type(path) ~= "table" then insertPoint = path else insertPoint = path[#path] for i = 1, #path - 1 do data = data[path[i]] if not data then return end end end for k in pairs(data) do if k ~= collapsed and k >= insertPoint then if not maxIndex or k > maxIndex then maxIndex = k end end end if maxIndex then -- may be nil if insertPoint is greater than the max of anything else for i = maxIndex, insertPoint, -1 do data[i + 1] = data[i] end end data[insertPoint] = {[collapsed] = value} end function OptionsPrivate.DuplicateCollapseData(id, namespace, path) collapsedOptions[id] = collapsedOptions[id] or {} collapsedOptions[id][namespace] = collapsedOptions[id][namespace] or {} if type(path) ~= "table" then if (collapsedOptions[id][namespace][path]) then tinsert(collapsedOptions[id][namespace], path + 1, CopyTable(collapsedOptions[id][namespace][path])) end else local tmp = collapsedOptions[id][namespace] local lastKey = tremove(path) for _, key in ipairs(path) do tmp[key] = tmp[key] or {} tmp = tmp[key] end if (tmp[lastKey]) then tinsert(tmp, lastKey + 1, CopyTable(tmp[lastKey])) end end end function OptionsPrivate.AddTextFormatOption(input, withHeader, get, addOption, hidden, setHidden, withoutColor, index, total) local headerOption if withHeader and (not index or index == 1) then headerOption = { type = "execute", control = "WeakAurasExpandSmall", name = L["|cffffcc00Format Options|r"], width = WeakAuras.doubleWidth, func = function(info, button) setHidden(not hidden()) end, image = function() return hidden() and "collapsed" or "expanded" end, imageWidth = 15, imageHeight = 15, arg = { expanderName = tostring(addOption) } } addOption("header", headerOption) else hidden = false end local seenSymbols = {} local parseFn = function(symbol) if not seenSymbols[symbol] then local _, sym = string.match(symbol, "(.+)%.(.+)") sym = sym or symbol if sym == "i" then -- No special options for these else addOption(symbol .. "desc", { type = "description", name = L["Format for %s"]:format("%" .. symbol), width = WeakAuras.normalWidth, hidden = hidden }) addOption(symbol .. "_format", { type = "select", name = L["Format"], width = WeakAuras.normalWidth, values = OptionsPrivate.Private.format_types_display, hidden = hidden, reloadOptions = true }) local selectedFormat = get(symbol .. "_format") if (OptionsPrivate.Private.format_types[selectedFormat]) then OptionsPrivate.Private.format_types[selectedFormat].AddOptions(symbol, hidden, addOption, get, withoutColor) end seenSymbols[symbol] = true end end end if type(input) == "table" then for _, txt in ipairs(input) do OptionsPrivate.Private.ParseTextStr(txt, parseFn) end else OptionsPrivate.Private.ParseTextStr(input, parseFn) end if withHeader and (not index or index == total) then addOption("header_anchor", { type = "description", name = "", control = "WeakAurasExpandAnchor", arg = { expanderName = tostring(addOption) } } ) end if not next(seenSymbols) and headerOption and not index then headerOption.hidden = true end return next(seenSymbols) ~= nil end