diff --git a/.vscode/bookmarks.json b/.vscode/bookmarks.json index 7b993080..707c4c0c 100644 --- a/.vscode/bookmarks.json +++ b/.vscode/bookmarks.json @@ -79,17 +79,17 @@ "label": "" }, { - "line": 1343, + "line": 1345, "column": 59, "label": "" }, { - "line": 1407, + "line": 1409, "column": 4, "label": "" }, { - "line": 1725, + "line": 1727, "column": 97, "label": "" } diff --git a/WorldQuestTracker.toc b/WorldQuestTracker.toc index 72a77380..a1ac5dbe 100644 --- a/WorldQuestTracker.toc +++ b/WorldQuestTracker.toc @@ -1,5 +1,5 @@ -## Interface: 100107 -## Interface-Mainline: 100107 +## Interface: 100200 +## Interface-Mainline: 100200 ## Interface-Wrath: 30401 ## Title: World Quest Tracker diff --git a/WorldQuestTracker_Core.lua b/WorldQuestTracker_Core.lua index 61e91c86..e453b2aa 100644 --- a/WorldQuestTracker_Core.lua +++ b/WorldQuestTracker_Core.lua @@ -1470,13 +1470,21 @@ WorldQuestTracker.OnToggleWorldMap = function(self) WQTPathFrame.texturePool = {} WQTPathFrame.texturesNotInUse = {} + --line size need to be dynamic with the canvas size - WQTPathFrame.Distance = WorldQuestTracker.db.profile.path.LineSize / WorldQuestTracker.db.profile.path.DotAmount + local dotScale = WorldQuestTracker.DotLineScale[WorldMapFrame.mapID] or 1 + local dotAmount = WorldQuestTracker.db.profile.path.DotAmount + dotAmount = math.floor(dotAmount * dotScale) + + WQTPathFrame.DotScale = dotScale + WQTPathFrame.Distance = WorldQuestTracker.db.profile.path.LineSize / (WorldQuestTracker.db.profile.path.DotAmount * dotAmount) WQTPathFrame.bIsShowingLine = false function WQTPathFrame.RefreshDot(Dot) + local dotSize = WorldQuestTracker.db.profile.path.DotSize + dotSize = dotSize * WQTPathFrame.DotScale Dot:SetTexture(WorldQuestTracker.db.profile.path.DotTexture) - Dot:SetSize(WorldQuestTracker.db.profile.path.DotSize, WorldQuestTracker.db.profile.path.DotSize) + Dot:SetSize(dotSize, dotSize) Dot:SetVertexColor(unpack(WorldQuestTracker.db.profile.path.ColorSRGB)) end @@ -1506,7 +1514,9 @@ WorldQuestTracker.OnToggleWorldMap = function(self) dotAmount = math.floor(dotAmount * dotScale) --line length + WQTPathFrame.DotScale = dotScale WQTPathFrame.Distance = WorldQuestTracker.db.profile.path.LineSize / dotAmount + WQTPathFrame.Distance = WQTPathFrame.Distance * 2 --if (not WQTPathFrame.bIsShowingLine) then -- worldQuestTrackerPathProvider:ShowLine() diff --git a/WorldQuestTracker_IDs.lua b/WorldQuestTracker_IDs.lua index 8b51cb1c..f86cfc0b 100644 --- a/WorldQuestTracker_IDs.lua +++ b/WorldQuestTracker_IDs.lua @@ -57,6 +57,7 @@ WorldQuestTracker.MapData.ZoneIDs = { WAKINGSHORES = 2022, FORBIDDENREACH = 2026, ZARALEK = 2133, + EMERALDDREAM = 2200, --Shadowlands MALDRAXXUS = 1536, @@ -101,284 +102,285 @@ WorldQuestTracker.MapData.ZoneIDs = { } --cache -local zoneQuests = WorldQuestTracker.MapData.ZoneIDs +local zoneIDs = WorldQuestTracker.MapData.ZoneIDs WorldQuestTracker.DotLineScale = { - [zoneQuests.ZARALEK] = 2, - [zoneQuests.OHNAHRANPLAINS] = 2, - [zoneQuests.WAKINGSHORES] = 2, - [zoneQuests.FORBIDDENREACH] = 2, - [zoneQuests.AZURESSPAN] = 2, - [zoneQuests.THALDRASZUS] = 2, - [zoneQuests.DRAGONISLES] = 2, + [zoneIDs.ZARALEK] = 2, + [zoneIDs.OHNAHRANPLAINS] = 2, + [zoneIDs.WAKINGSHORES] = 2, + [zoneIDs.FORBIDDENREACH] = 2, + [zoneIDs.AZURESSPAN] = 2, + [zoneIDs.THALDRASZUS] = 2, + [zoneIDs.DRAGONISLES] = 2, [2112] = 2, --valdrakken + [zoneIDs.EMERALDDREAM] = 2, } --all zones with world quests WorldQuestTracker.MapData.WorldQuestZones = { --Dragonflight - [zoneQuests.AZURESSPAN] = true, - [zoneQuests.THALDRASZUS] = true, - [zoneQuests.OHNAHRANPLAINS] = true, - [zoneQuests.WAKINGSHORES] = true, - [zoneQuests.FORBIDDENREACH] = true, - [zoneQuests.ZARALEK] = true, + [zoneIDs.AZURESSPAN] = true, + [zoneIDs.THALDRASZUS] = true, + [zoneIDs.OHNAHRANPLAINS] = true, + [zoneIDs.WAKINGSHORES] = true, + [zoneIDs.FORBIDDENREACH] = true, + [zoneIDs.ZARALEK] = true, --Shadowlands - [zoneQuests.MALDRAXXUS] = true, - [zoneQuests.BASTION] = true, - [zoneQuests.ARDENWEALD] = true, - [zoneQuests.REVENDRETH] = true, - [zoneQuests.THEMAW] = true, - [zoneQuests.ZERETH] = true, + [zoneIDs.MALDRAXXUS] = true, + [zoneIDs.BASTION] = true, + [zoneIDs.ARDENWEALD] = true, + [zoneIDs.REVENDRETH] = true, + [zoneIDs.THEMAW] = true, + [zoneIDs.ZERETH] = true, --[zoneQuests.ORIBOS] = true, --BFA --zandalar - [zoneQuests.ZULDAZAAR] = true, - [zoneQuests.NAZMIR] = true, - [zoneQuests.VOLDUN] = true, + [zoneIDs.ZULDAZAAR] = true, + [zoneIDs.NAZMIR] = true, + [zoneIDs.VOLDUN] = true, [1165] = true, --dazar'alor [1161] = true, --boralus - [zoneQuests.TOLDAGOR] = true, + [zoneIDs.TOLDAGOR] = true, --kul'tiras - [zoneQuests.TIRAGARDE] = true, - [zoneQuests.STORMSONG] = true, - [zoneQuests.DRUSTVAR] = true, + [zoneIDs.TIRAGARDE] = true, + [zoneIDs.STORMSONG] = true, + [zoneIDs.DRUSTVAR] = true, --PRE PATCH - [zoneQuests.DARKSHORE] = true, - [zoneQuests.ARATHI] = true, + [zoneIDs.DARKSHORE] = true, + [zoneIDs.ARATHI] = true, --8.2 - [zoneQuests.NAZJATAR] = true, - [zoneQuests.MECHAGON] = true, + [zoneIDs.NAZJATAR] = true, + [zoneIDs.MECHAGON] = true, --Legion --broken isles - [zoneQuests.AZSUNA] = true, - [zoneQuests.HIGHMONTAIN] = true, - [zoneQuests.STORMHEIM] = true, - [zoneQuests.SURAMAR] = true, - [zoneQuests.VALSHARAH] = true, - [zoneQuests.EYEAZSHARA] = true, - [zoneQuests.DALARAN] = true, - [zoneQuests.BROKENSHORE] = true, + [zoneIDs.AZSUNA] = true, + [zoneIDs.HIGHMONTAIN] = true, + [zoneIDs.STORMHEIM] = true, + [zoneIDs.SURAMAR] = true, + [zoneIDs.VALSHARAH] = true, + [zoneIDs.EYEAZSHARA] = true, + [zoneIDs.DALARAN] = true, + [zoneIDs.BROKENSHORE] = true, --argus - [zoneQuests.ANTORAN] = true, - [zoneQuests.KROKUUN] = true, - [zoneQuests.MCCAREE] = true, + [zoneIDs.ANTORAN] = true, + [zoneIDs.KROKUUN] = true, + [zoneIDs.MCCAREE] = true, } WorldQuestTracker.MapData.DragonflightZones = { - [zoneQuests.AZURESSPAN] = true, - [zoneQuests.THALDRASZUS] = true, - [zoneQuests.OHNAHRANPLAINS] = true, - [zoneQuests.WAKINGSHORES] = true, - [zoneQuests.FORBIDDENREACH] = true, - [zoneQuests.ZARALEK] = true, - [zoneQuests.DRAGONISLES] = true, + [zoneIDs.AZURESSPAN] = true, + [zoneIDs.THALDRASZUS] = true, + [zoneIDs.OHNAHRANPLAINS] = true, + [zoneIDs.WAKINGSHORES] = true, + [zoneIDs.FORBIDDENREACH] = true, + [zoneIDs.ZARALEK] = true, + [zoneIDs.DRAGONISLES] = true, [2112] = true, --valdrakken } --quest hub by expansion WorldQuestTracker.MapData.ExpMaps = { - [zoneQuests.DRAGONISLES] = 10, - [zoneQuests.THESHADOWLANDS] = 9, - [zoneQuests.ZANDALAR] = 8, - [zoneQuests.KULTIRAS] = 8, - [zoneQuests.AZEROTH] = 8, - [zoneQuests.BROKENISLES] = 7, + [zoneIDs.DRAGONISLES] = 10, + [zoneIDs.THESHADOWLANDS] = 9, + [zoneIDs.ZANDALAR] = 8, + [zoneIDs.KULTIRAS] = 8, + [zoneIDs.AZEROTH] = 8, + [zoneIDs.BROKENISLES] = 7, } --list of map ids for world quest hubs WorldQuestTracker.MapData.QuestHubs = { - [zoneQuests.DRAGONISLES] = true, --dragon isles hub - [zoneQuests.THESHADOWLANDS] = true, --shadowlands hub - [zoneQuests.BROKENISLES] = true, --dalaran (~rev) - [zoneQuests.ARGUS] = true, --argus (~rev) - [zoneQuests.ZANDALAR] = true, --bfa horde - [zoneQuests.KULTIRAS] = true, --bfa alliance - [zoneQuests.AZEROTH] = true, --main hub + [zoneIDs.DRAGONISLES] = true, --dragon isles hub + [zoneIDs.THESHADOWLANDS] = true, --shadowlands hub + [zoneIDs.BROKENISLES] = true, --dalaran (~rev) + [zoneIDs.ARGUS] = true, --argus (~rev) + [zoneIDs.ZANDALAR] = true, --bfa horde + [zoneIDs.KULTIRAS] = true, --bfa alliance + [zoneIDs.AZEROTH] = true, --main hub } --world map anchors WorldQuestTracker.mapTables = { --Dragon Isles (Dragonflight) - [zoneQuests.AZURESSPAN] = { + [zoneIDs.AZURESSPAN] = { widgets = {}, Anchor_X = 0.995, Anchor_Y = 0.48, GrowRight = false, show_on_map = { - [zoneQuests.DRAGONISLES] = true, + [zoneIDs.DRAGONISLES] = true, }, }, - [zoneQuests.THALDRASZUS] = { + [zoneIDs.THALDRASZUS] = { widgets = {}, Anchor_X = 0.995, Anchor_Y = 0.35, GrowRight = false, show_on_map = { - [zoneQuests.DRAGONISLES] = true, + [zoneIDs.DRAGONISLES] = true, }, }, - [zoneQuests.ZARALEK] = { + [zoneIDs.ZARALEK] = { widgets = {}, Anchor_X = 0.995, Anchor_Y = 0.7, GrowRight = false, show_on_map = { - [zoneQuests.DRAGONISLES] = true, + [zoneIDs.DRAGONISLES] = true, }, }, - [zoneQuests.OHNAHRANPLAINS] = { + [zoneIDs.OHNAHRANPLAINS] = { widgets = {}, Anchor_X = 0.002, Anchor_Y = 0.48, GrowRight = true, show_on_map = { - [zoneQuests.DRAGONISLES] = true, + [zoneIDs.DRAGONISLES] = true, }, }, - [zoneQuests.WAKINGSHORES] = { + [zoneIDs.WAKINGSHORES] = { widgets = {}, Anchor_X = 0.002, Anchor_Y = 0.28, GrowRight = true, show_on_map = { - [zoneQuests.DRAGONISLES] = true, + [zoneIDs.DRAGONISLES] = true, }, }, - [zoneQuests.FORBIDDENREACH] = { + [zoneIDs.FORBIDDENREACH] = { widgets = {}, Anchor_X = 0.6, Anchor_Y = 0.05, GrowRight = false, show_on_map = { - [zoneQuests.DRAGONISLES] = true, + [zoneIDs.DRAGONISLES] = true, }, }, --Shadowlands - [zoneQuests.ZERETH] = { + [zoneIDs.ZERETH] = { widgets = {}, Anchor_X = 0.995, Anchor_Y = 0.68, GrowRight = false, show_on_map = { - [zoneQuests.THESHADOWLANDS] = true, + [zoneIDs.THESHADOWLANDS] = true, }, }, - [zoneQuests.MALDRAXXUS] = { + [zoneIDs.MALDRAXXUS] = { widgets = {}, Anchor_X = 0.995, Anchor_Y = 0.28, GrowRight = false, show_on_map = { - [zoneQuests.THESHADOWLANDS] = true, + [zoneIDs.THESHADOWLANDS] = true, }, }, - [zoneQuests.BASTION] = { + [zoneIDs.BASTION] = { widgets = {}, Anchor_X = 0.995, Anchor_Y = 0.46, GrowRight = false, show_on_map = { - [zoneQuests.THESHADOWLANDS] = true, + [zoneIDs.THESHADOWLANDS] = true, }, }, - [zoneQuests.ARDENWEALD] = { + [zoneIDs.ARDENWEALD] = { widgets = {}, Anchor_X = 0.002, Anchor_Y = 0.70, GrowRight = true, show_on_map = { - [zoneQuests.THESHADOWLANDS] = true, + [zoneIDs.THESHADOWLANDS] = true, }, }, - [zoneQuests.REVENDRETH] = { + [zoneIDs.REVENDRETH] = { widgets = {}, Anchor_X = 0.002, Anchor_Y = 0.34, GrowRight = true, show_on_map = { - [zoneQuests.THESHADOWLANDS] = true, + [zoneIDs.THESHADOWLANDS] = true, }, }, - [zoneQuests.THEMAW] = { + [zoneIDs.THEMAW] = { widgets = {}, --Anchor_X = 0.31, Anchor_X = 0.002, Anchor_Y = 0.17, GrowRight = true, show_on_map = { - [zoneQuests.THESHADOWLANDS] = true, + [zoneIDs.THESHADOWLANDS] = true, }, }, --Main Hub - [zoneQuests.ZANDALAR] = { + [zoneIDs.ZANDALAR] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.55, GrowRight = true, show_on_map = { - [zoneQuests.AZEROTH] = true, + [zoneIDs.AZEROTH] = true, }, }, - [zoneQuests.KULTIRAS] = { + [zoneIDs.KULTIRAS] = { widgets = {}, Anchor_X = 0.99, Anchor_Y = 0.15, GrowRight = false, show_on_map = { - [zoneQuests.AZEROTH] = true, + [zoneIDs.AZEROTH] = true, }, }, - [zoneQuests.DARKSHORE] = { + [zoneIDs.DARKSHORE] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.32, GrowRight = true, show_on_map = { - [zoneQuests.AZEROTH] = true, + [zoneIDs.AZEROTH] = true, }, }, - [zoneQuests.ARATHI] = { + [zoneIDs.ARATHI] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.20, GrowRight = true, show_on_map = { - [zoneQuests.AZEROTH] = true, + [zoneIDs.AZEROTH] = true, }, }, - [zoneQuests.NAZJATAR] = { + [zoneIDs.NAZJATAR] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.08, GrowRight = true, show_on_map = { - [zoneQuests.AZEROTH] = true, + [zoneIDs.AZEROTH] = true, }, }, @@ -389,7 +391,7 @@ WorldQuestTracker.mapTables = { Anchor_Y = 0.48, GrowRight = false, show_on_map = { - [zoneQuests.KULTIRAS] = true, + [zoneIDs.KULTIRAS] = true, }, }, @@ -399,172 +401,172 @@ WorldQuestTracker.mapTables = { Anchor_Y = 0.48, GrowRight = false, show_on_map = { - [zoneQuests.ZANDALAR] = true, + [zoneIDs.ZANDALAR] = true, } }, --alliance - [zoneQuests.TIRAGARDE] = { + [zoneIDs.TIRAGARDE] = { widgets = {}, Anchor_X = 0.99, Anchor_Y = 0.62, GrowRight = false, show_on_map = { - [zoneQuests.KULTIRAS] = true, + [zoneIDs.KULTIRAS] = true, }, }, - [zoneQuests.STORMSONG] = { + [zoneIDs.STORMSONG] = { widgets = {}, Anchor_X = 0.99, Anchor_Y = 0.18, GrowRight = false, show_on_map = { - [zoneQuests.KULTIRAS] = true, + [zoneIDs.KULTIRAS] = true, }, }, - [zoneQuests.DRUSTVAR] = { + [zoneIDs.DRUSTVAR] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.36, GrowRight = true, show_on_map = { - [zoneQuests.KULTIRAS] = true, + [zoneIDs.KULTIRAS] = true, }, }, - [zoneQuests.TOLDAGOR] = { + [zoneIDs.TOLDAGOR] = { widgets = {}, Anchor_X = 0.99, Anchor_Y = 0.56, GrowRight = false, show_on_map = { - [zoneQuests.KULTIRAS] = true, + [zoneIDs.KULTIRAS] = true, }, }, --horde - [zoneQuests.ZULDAZAAR] = { + [zoneIDs.ZULDAZAAR] = { widgets = {}, Anchor_X = 0.99, Anchor_Y = 0.62, GrowRight = false, show_on_map = { - [zoneQuests.ZANDALAR] = true, + [zoneIDs.ZANDALAR] = true, } }, - [zoneQuests.NAZMIR] = { + [zoneIDs.NAZMIR] = { widgets = {}, Anchor_X = 0.99, Anchor_Y = 0.18, GrowRight = false, show_on_map = { - [zoneQuests.ZANDALAR] = true, + [zoneIDs.ZANDALAR] = true, } }, - [zoneQuests.VOLDUN] = { + [zoneIDs.VOLDUN] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.36, GrowRight = true, show_on_map = { - [zoneQuests.ZANDALAR] = true, + [zoneIDs.ZANDALAR] = true, } }, --Legion - [zoneQuests.AZSUNA] = { + [zoneIDs.AZSUNA] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.53, GrowRight = true, show_on_map = { - [zoneQuests.BROKENISLES] = true, + [zoneIDs.BROKENISLES] = true, } }, - [zoneQuests.VALSHARAH] = { + [zoneIDs.VALSHARAH] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.33, GrowRight = true, show_on_map = { - [zoneQuests.BROKENISLES] = true, + [zoneIDs.BROKENISLES] = true, } }, - [zoneQuests.HIGHMONTAIN] = { + [zoneIDs.HIGHMONTAIN] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.10, GrowRight = true, show_on_map = { - [zoneQuests.BROKENISLES] = true, + [zoneIDs.BROKENISLES] = true, } }, - [zoneQuests.STORMHEIM] = { + [zoneIDs.STORMHEIM] = { widgets = {}, Anchor_X = 0.99, Anchor_Y = 0.30, show_on_map = { - [zoneQuests.BROKENISLES] = true, + [zoneIDs.BROKENISLES] = true, } }, - [zoneQuests.SURAMAR] = { + [zoneIDs.SURAMAR] = { widgets = {}, Anchor_X = 0.99, Anchor_Y = 0.50, show_on_map = { - [zoneQuests.BROKENISLES] = true, + [zoneIDs.BROKENISLES] = true, } }, - [zoneQuests.BROKENSHORE] = { --broken shore + [zoneIDs.BROKENSHORE] = { --broken shore widgets = {}, Anchor_X = 0.99, Anchor_Y = 0.69, show_on_map = { - [zoneQuests.BROKENISLES] = true, + [zoneIDs.BROKENISLES] = true, } }, - [zoneQuests.EYEAZSHARA] = { + [zoneIDs.EYEAZSHARA] = { widgets = {}, Anchor_X = 0.5, Anchor_Y = 0.8, GrowRight = true, show_on_map = { - [zoneQuests.BROKENISLES] = true, + [zoneIDs.BROKENISLES] = true, } }, - [zoneQuests.DALARAN] = { + [zoneIDs.DALARAN] = { widgets = {}, Anchor_X = 0.47, Anchor_Y = 0.62, GrowRight = true, show_on_map = { - [zoneQuests.BROKENISLES] = true, + [zoneIDs.BROKENISLES] = true, } }, - [zoneQuests.MCCAREE] = { + [zoneIDs.MCCAREE] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.20, show_on_map = { - [zoneQuests.ARGUS] = true, + [zoneIDs.ARGUS] = true, }, GrowRight = true, }, - [zoneQuests.ANTORAN] = { + [zoneIDs.ANTORAN] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.37, show_on_map = { - [zoneQuests.ARGUS] = true, + [zoneIDs.ARGUS] = true, }, GrowRight = true, }, - [zoneQuests.KROKUUN] = { + [zoneIDs.KROKUUN] = { widgets = {}, Anchor_X = 0.01, Anchor_Y = 0.52, show_on_map = { - [zoneQuests.ARGUS] = true, + [zoneIDs.ARGUS] = true, }, GrowRight = true, }, @@ -622,19 +624,19 @@ WorldQuestTracker.MapData.ResourceIcons = { --which faction set to be used by the map id --this table isn't being in use at the moment WorldQuestTracker.MapData.FactionByMapID = { --not in use - [zoneQuests.ZANDALAR] = "BFA", - [zoneQuests.KULTIRAS] = "BFA", - [zoneQuests.AZEROTH] = "BFA", + [zoneIDs.ZANDALAR] = "BFA", + [zoneIDs.KULTIRAS] = "BFA", + [zoneIDs.AZEROTH] = "BFA", [619] = "LEGION", --brosken isles map [905] = "LEGION", --argus map - [zoneQuests.ZULDAZAAR] = "BFA", - [zoneQuests.NAZMIR] = "BFA", - [zoneQuests.VOLDUN] = "BFA", - [zoneQuests.TIRAGARDE] = "BFA", - [zoneQuests.STORMSONG] = "BFA", - [zoneQuests.DRUSTVAR] = "BFA", + [zoneIDs.ZULDAZAAR] = "BFA", + [zoneIDs.NAZMIR] = "BFA", + [zoneIDs.VOLDUN] = "BFA", + [zoneIDs.TIRAGARDE] = "BFA", + [zoneIDs.STORMSONG] = "BFA", + [zoneIDs.DRUSTVAR] = "BFA", } --start of factions @@ -741,11 +743,11 @@ WorldQuestTracker.MapData.AllFactionIds = { ---@type table WorldQuestTracker.MapData.FactionMapId = { --Dragonflight - [2503] = zoneQuests.OHNAHRANPLAINS, --Maruuk Centaur - [2507] = zoneQuests.WAKINGSHORES, --Dragonscale Expedition - [2510] = zoneQuests.THALDRASZUS, --Valdrakken Accord - [2511] = zoneQuests.AZURESSPAN, --Iskaara Tuskarr - [2564] = zoneQuests.ZARALEK, --Loamm Niffen + [2503] = zoneIDs.OHNAHRANPLAINS, --Maruuk Centaur + [2507] = zoneIDs.WAKINGSHORES, --Dragonscale Expedition + [2510] = zoneIDs.THALDRASZUS, --Valdrakken Accord + [2511] = zoneIDs.AZURESSPAN, --Iskaara Tuskarr + [2564] = zoneIDs.ZARALEK, --Loamm Niffen --Shadowlands [2410] = 1536, --The Undying Army | MALDRAXXUS @@ -869,36 +871,36 @@ local LegionFactions = { --what are the factionIds belong to the map WorldQuestTracker.MapData.ReputationByMap = { --world maps - [zoneQuests.DRAGONISLES] = DragonflightFactions, - [zoneQuests.THESHADOWLANDS] = ShadowlandsFactions, - [zoneQuests.KULTIRAS] = BFAFactions, - [zoneQuests.ZANDALAR] = BFAFactions, - [zoneQuests.AZEROTH] = BFAFactions, - [zoneQuests.BROKENISLES] = LegionFactions, - [zoneQuests.ARGUS] = LegionFactions, + [zoneIDs.DRAGONISLES] = DragonflightFactions, + [zoneIDs.THESHADOWLANDS] = ShadowlandsFactions, + [zoneIDs.KULTIRAS] = BFAFactions, + [zoneIDs.ZANDALAR] = BFAFactions, + [zoneIDs.AZEROTH] = BFAFactions, + [zoneIDs.BROKENISLES] = LegionFactions, + [zoneIDs.ARGUS] = LegionFactions, --zones --Dragonflight - [zoneQuests.OHNAHRANPLAINS] = DragonflightFactions, - [zoneQuests.WAKINGSHORES] = DragonflightFactions, - [zoneQuests.THALDRASZUS] = DragonflightFactions, - [zoneQuests.AZURESSPAN] = DragonflightFactions, - [zoneQuests.ZARALEK] = DragonflightFactions, + [zoneIDs.OHNAHRANPLAINS] = DragonflightFactions, + [zoneIDs.WAKINGSHORES] = DragonflightFactions, + [zoneIDs.THALDRASZUS] = DragonflightFactions, + [zoneIDs.AZURESSPAN] = DragonflightFactions, + [zoneIDs.ZARALEK] = DragonflightFactions, --Shadowlands - [zoneQuests.BASTION] = ShadowlandsFactions, - [zoneQuests.MALDRAXXUS] = ShadowlandsFactions, - [zoneQuests.ARDENWEALD] = ShadowlandsFactions, - [zoneQuests.REVENDRETH] = ShadowlandsFactions, - [zoneQuests.ZERETH] = ShadowlandsFactions, + [zoneIDs.BASTION] = ShadowlandsFactions, + [zoneIDs.MALDRAXXUS] = ShadowlandsFactions, + [zoneIDs.ARDENWEALD] = ShadowlandsFactions, + [zoneIDs.REVENDRETH] = ShadowlandsFactions, + [zoneIDs.ZERETH] = ShadowlandsFactions, --BFA - [zoneQuests.ZULDAZAAR] = BFAFactions, - [zoneQuests.NAZMIR] = BFAFactions, - [zoneQuests.VOLDUN] = BFAFactions, - [zoneQuests.TIRAGARDE] = BFAFactions, - [zoneQuests.STORMSONG] = BFAFactions, - [zoneQuests.DRUSTVAR] = BFAFactions, + [zoneIDs.ZULDAZAAR] = BFAFactions, + [zoneIDs.NAZMIR] = BFAFactions, + [zoneIDs.VOLDUN] = BFAFactions, + [zoneIDs.TIRAGARDE] = BFAFactions, + [zoneIDs.STORMSONG] = BFAFactions, + [zoneIDs.DRUSTVAR] = BFAFactions, } function WorldQuestTracker.GetFactionsAllowedOnMap(mapId) diff --git a/WorldQuestTracker_Initialize.lua b/WorldQuestTracker_Initialize.lua index 65273434..0b1fcca9 100644 --- a/WorldQuestTracker_Initialize.lua +++ b/WorldQuestTracker_Initialize.lua @@ -167,7 +167,7 @@ do summary_scale = 0.95, summary_showby = "bytype", --"bytype" or "byzone" summary_anchor = "left", - summary_widgets_per_row = 10, + summary_widgets_per_row = 8, }, disable_world_map_widgets = false, diff --git a/WorldQuestTracker_MapAPI.lua b/WorldQuestTracker_MapAPI.lua index 15cdd3bb..403e7fb3 100644 --- a/WorldQuestTracker_MapAPI.lua +++ b/WorldQuestTracker_MapAPI.lua @@ -79,9 +79,6 @@ function WorldQuestTracker.CheckQuestRewardDataForWidget (widget, noScheduleRefr end if (not HaveQuestRewardData (questID)) then - - - --if this is from a re-schedule it already requested the data if (not noRequestData) then --ask que server for the reward data diff --git a/libs/DF/addon.examples.lua b/libs/DF/addon.examples.lua new file mode 100644 index 00000000..47d1cc04 --- /dev/null +++ b/libs/DF/addon.examples.lua @@ -0,0 +1,53 @@ + +--[=[ +for this example, let's consider the name of your addon is "Slice And Dance" +addon folder name on World of Warcraft/_retail_/Interface/AddOns/SliceAndDance/ +toc file example /World of Warcraft/_retail_/Interface/AddOns/SliceAndDance/SliceAndDance.toc: + ## Interface: 100107 + ## Title: "Slice And Dance" + ## Notes: Show a slice and dice bar for rogues + ## SavedVariables: SliceAndDanceDatabase + SliceAndDance_Core.lua +--]=] + +--create a new file on your addon folder called "SliceAndDance_Core.lua" +--this are the contents to add to the file + +--each file of your addon receives a payload with the addon name and a private table +--this private table is shared between all files of your addon and cannot be accessed by other addons or scripts +--the addon name is the ToC name of your addon, the toc is also the folder name of your addon in the addons folder +local addonName, priviteTable = ... + +--saved variables name +--this is the name of the global table where your addon will store it's saved variables +--you define this on the addon.toc file under the line "## SavedVariables: SliceAndDanceDatabase" +local savedVariablesName = "SliceAndDanceDatabase" + +--default settings +--a simple table with default settings for your addon, any value modified by the user will be stored in the saved variables table +--if your addon doesn't have any settings, you can just pass an empty table +local defaultSettingsExample = { + width = 500, + height = 500, + name = "John", +} + +local detailsFramework = DetailsFramework +--create the core addon object, this is a table which will all public functions of your addon +local myNewAddonObject = detailsFramework:CreateNewAddOn(addonName, savedVariablesName, defaultSettingsExample) + +--by default, the table generated by the CreateNewAddOn() isn't placed in the global environment +--if this is desired for some reason, you can do it manually +_G.MyNewAddon = myNewAddonObject + +--this function is called when the savedVariables of your addon is ready to be used +function myNewAddonObject.OnLoad(self, profile) + --self is: myNewAddonObject + --profile is a table with defaultSettingsExample +end + +--this function is called when when the loading screen is done and the player character is ready to play +function myNewAddonObject.OnInit(self, profile) --fired from detailsFramework at PLAYER_LOGIN + --self is: myNewAddonObject + --profile is a table with defaultSettingsExample +end \ No newline at end of file diff --git a/libs/DF/addon.lua b/libs/DF/addon.lua index 8285d6dc..d41e7ec2 100644 --- a/libs/DF/addon.lua +++ b/libs/DF/addon.lua @@ -119,7 +119,7 @@ function detailsFramework:CreateNewAddOn(addonName, globalSavedVariablesName, sa end ---old create addon +--old create addon using ace3 function detailsFramework:CreateAddOn(name, global_saved, global_table, options_table, broker) local addon = LibStub("AceAddon-3.0"):NewAddon (name, "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0", "DetailsFramework-1.0", "AceComm-3.0") diff --git a/libs/DF/auras.lua b/libs/DF/auras.lua index fc01800d..9a694489 100644 --- a/libs/DF/auras.lua +++ b/libs/DF/auras.lua @@ -1,128 +1,134 @@ +---@type detailsframework local DF = _G ["DetailsFramework"] if (not DF or not DetailsFrameworkCanLoad) then - return + return end +local detailsFramework = DF + local _ -local tinsert = tinsert +local tinsert = table.insert local GetSpellInfo = GetSpellInfo local lower = string.lower local GetSpellBookItemInfo = GetSpellBookItemInfo +local unpack = unpack +local CreateFrame = CreateFrame +local GameTooltip = GameTooltip +local tremove = tremove -local CONST_MAX_SPELLS = 450000 +local CONST_MAX_SPELLS = 500000 -function DF:GetAuraByName (unit, spellName, isDebuff) +function DF:GetAuraByName(unit, spellName, isDebuff) isDebuff = isDebuff and "HARMFUL|PLAYER" for i = 1, 40 do - local name, texture, count, debuffType, duration, expirationTime, caster, canStealOrPurge, nameplateShowPersonal, spellId, canApplyAura, isBossDebuff, isCastByPlayer, nameplateShowAll = UnitAura (unit, i, isDebuff) + local name, texture, count, debuffType, duration, expirationTime, caster, canStealOrPurge, nameplateShowPersonal, spellId, canApplyAura, isBossDebuff, isCastByPlayer, nameplateShowAll = UnitAura(unit, i, isDebuff) if (not name) then return end - + if (name == spellName) then return name, texture, count, debuffType, duration, expirationTime, caster, canStealOrPurge, nameplateShowPersonal, spellId, canApplyAura, isBossDebuff, isCastByPlayer, nameplateShowAll end end end -local default_text_for_aura_frame = { - AUTOMATIC = "Automatic", - MANUAL = "Manual", - METHOD = "Aura Tracking Method:", +local defaultTextForAuraFrame = { + AUTOMATIC = "Automatic", + MANUAL = "Manual", + METHOD = "Aura Tracking Method:", BUFFS_IGNORED = "Buffs Ignored", DEBUFFS_IGNORED = "Debuffs Ignored", BUFFS_TRACKED = "Buffs Tracked", DEBUFFS_TRACKED = "Debuffs Tracked", - + AUTOMATIC_DESC = "Auras are being tracked automatically, the addon controls what to show.\nYou may add auras to the blacklist or add extra auras to track.", MANUAL_DESC = "Auras are being tracked manually, the addon only check for auras you entered below.", - + MANUAL_ADD_BLACKLIST_BUFF = "Add Buff to Blacklist", MANUAL_ADD_BLACKLIST_DEBUFF = "Add Debuff to Blacklist", MANUAL_ADD_TRACKLIST_BUFF = "Add Buff to Tracklist", MANUAL_ADD_TRACKLIST_DEBUFF = "Add Debuff to Tracklist", } -function DF:LoadAllSpells (hashMap, indexTable, allSpellsSameName) - - --pre checking which tables to fill to avoid checking if the table exists during the gigantic loop for performance - - if (not DF.LoadingAuraAlertFrame) then - DF.LoadingAuraAlertFrame = CreateFrame("frame", "DetailsFrameworkLoadingAurasAlert", UIParent, "BackdropTemplate") - DF.LoadingAuraAlertFrame:SetSize(340, 75) - DF.LoadingAuraAlertFrame:SetPoint("center") - DF.LoadingAuraAlertFrame:SetFrameStrata("TOOLTIP") - DF:ApplyStandardBackdrop(DF.LoadingAuraAlertFrame) - DF.LoadingAuraAlertFrame:SetBackdropBorderColor(1, 0.8, 0.1) - - DF.LoadingAuraAlertFrame.IsLoadingLabel1 = DF:CreateLabel(DF.LoadingAuraAlertFrame, "We are currently loading spell names and spell IDs") - DF.LoadingAuraAlertFrame.IsLoadingLabel2 = DF:CreateLabel(DF.LoadingAuraAlertFrame, "This may take only a few seconds") - DF.LoadingAuraAlertFrame.IsLoadingImage1 = DF:CreateImage(DF.LoadingAuraAlertFrame, [[Interface\DialogFrame\UI-Dialog-Icon-AlertOther]], 32, 32) - DF.LoadingAuraAlertFrame.IsLoadingLabel1.align = "center" - DF.LoadingAuraAlertFrame.IsLoadingLabel2.align = "center" - - DF.LoadingAuraAlertFrame.IsLoadingLabel1:SetPoint("center", 16, 10) - DF.LoadingAuraAlertFrame.IsLoadingLabel2:SetPoint("center", 16, -5) - DF.LoadingAuraAlertFrame.IsLoadingImage1:SetPoint("left", 10, 0) - end +--store spell caches, they load empty and are filled when an addon require a cache with all spells +local spellsHashMap +local spellsIndexTable +local spellsWithSameName - DF.LoadingAuraAlertFrame:Show() - - C_Timer.After(0, function() - if (hashMap and not indexTable) then - for i = 1, CONST_MAX_SPELLS do - local spellName = GetSpellInfo(i) - if (spellName) then - hashMap [lower (spellName)] = i - end - end - - elseif (not hashMap and indexTable) then - for i = 1, CONST_MAX_SPELLS do - local spellName = GetSpellInfo(i) - if (spellName) then - indexTable [#indexTable+1] = lower (spellName) - end - end - - elseif (hashMap and indexTable) then - if (allSpellsSameName) then - for i = 1, CONST_MAX_SPELLS do - local spellName = GetSpellInfo(i) - if (spellName) then - spellName = lower (spellName) - indexTable [#indexTable + 1] = spellName - hashMap [spellName] = i - - --same name table - local sameNameTable = allSpellsSameName [spellName] - if (not sameNameTable) then - sameNameTable = {} - allSpellsSameName [spellName] = sameNameTable - end - sameNameTable [#sameNameTable + 1] = i - end - end - else - for i = 1, CONST_MAX_SPELLS do - local spellName = GetSpellInfo(i) - if (spellName) then - spellName = lower (spellName) - indexTable [#indexTable + 1] = spellName - hashMap [spellName] = i - end - end +function DF:GetSpellCaches() + return spellsHashMap, spellsIndexTable, spellsWithSameName +end + +local lazyLoadAllSpells = function(payload, iterationCount, maxIterations) + local startPoint = payload.nextIndex + --the goal is iterate over 500000 spell ids over 200 frames + local endPoint = startPoint + 2500 + payload.nextIndex = endPoint + local i = startPoint + 1 + + --make upvalues be closer + local toLowerCase = string.lower + local GetSpellInfo = GetSpellInfo + + local hashMap = payload.hashMap + local indexTable = payload.indexTable + local allSpellsSameName = payload.allSpellsSameName + + while (i < endPoint) do + local spellName = GetSpellInfo(i) + + if (spellName) then + spellName = toLowerCase(spellName) + hashMap[spellName] = i --[spellname] = spellId + indexTable[#indexTable+1] = spellName --array with all spellnames + + local spellNameTable = allSpellsSameName[spellName] + if (not spellNameTable) then + spellNameTable = {} + allSpellsSameName[spellName] = spellNameTable end + spellNameTable[#spellNameTable+1] = i end - - DF.LoadingAuraAlertFrame:Hide() - end) + i = i + 1 + end +end + +function DF:UnloadSpellCache() + if (spellsHashMap) then + table.wipe(spellsHashMap) + table.wipe(spellsIndexTable) + table.wipe(spellsWithSameName) + end end -local cleanfunction = function() end +function DF:LoadSpellCache(hashMap, indexTable, allSpellsSameName) + if (spellsHashMap and next(spellsHashMap)) then + --return the already loaded cache + return spellsHashMap, spellsIndexTable, spellsWithSameName + end + + assert(type(hashMap) == "table", "DetailsFramework:LoadSpellCache(): require a table on #1 parameter.") + assert(type(indexTable) == "table", "DetailsFramework:LoadSpellCache(): require a table on #2 parameter.") + assert(type(allSpellsSameName) == "table", "DetailsFramework:LoadSpellCache(): require a table on #3 parameter.") + + spellsHashMap = hashMap + spellsIndexTable = indexTable + spellsWithSameName = allSpellsSameName + + local iterations = 200 + local payload = { + nextIndex = 0, + hashMap = hashMap, + indexTable = indexTable, + allSpellsSameName = allSpellsSameName, + } + detailsFramework.Schedules.LazyExecute(lazyLoadAllSpells, payload, iterations) + + return spellsHashMap, spellsIndexTable, spellsWithSameName +end do local metaPrototype = { @@ -135,7 +141,7 @@ do --get the already existing metaPrototype local oldMetaPrototype = _G[DF.GlobalWidgetControlNames["aura_tracker"]] --check if is older - if ( (not oldMetaPrototype.dversion) or (oldMetaPrototype.dversion < DF.dversion) ) then + if ((not oldMetaPrototype.dversion) or(oldMetaPrototype.dversion < DF.dversion) ) then --the version is older them the currently loading one --copy the new values into the old metatable for funcName, _ in pairs(metaPrototype) do @@ -152,40 +158,39 @@ local AuraTrackerMetaFunctions = _G[DF.GlobalWidgetControlNames["aura_tracker"]] DF:Mixin(AuraTrackerMetaFunctions, DF.ScriptHookMixin) --create panels -local on_profile_changed = function(self, newdb) - +local onProfileChangedCallback = function(self, newdb) self.db = newdb --automatic - self.buff_ignored:DoSetData (newdb.aura_tracker.buff_banned) - self.debuff_ignored:DoSetData (newdb.aura_tracker.debuff_banned) - self.buff_tracked:DoSetData (newdb.aura_tracker.buff_tracked) - self.debuff_tracked:DoSetData (newdb.aura_tracker.debuff_tracked) - - self.buff_ignored:DoRefresh() - self.debuff_ignored:DoRefresh() - self.buff_tracked:DoRefresh() - self.debuff_tracked:DoRefresh() - + self.buff_ignored:SetData(newdb.aura_tracker.buff_banned) + self.debuff_ignored:SetData(newdb.aura_tracker.debuff_banned) + self.buff_tracked:SetData(newdb.aura_tracker.buff_tracked) + self.debuff_tracked:SetData(newdb.aura_tracker.debuff_tracked) + + self.buff_ignored:Refresh() + self.debuff_ignored:Refresh() + self.buff_tracked:Refresh() + self.debuff_tracked:Refresh() + --manual - self.buffs_added:SetData (newdb.aura_tracker.buff) - self.debuffs_added:SetData (newdb.aura_tracker.debuff) + self.buffs_added:SetData(newdb.aura_tracker.buff) + self.debuffs_added:SetData(newdb.aura_tracker.debuff) self.buffs_added:Refresh() self.debuffs_added:Refresh() - + --method if (newdb.aura_tracker.track_method == 0x1) then self.f_auto:Show() self.f_manual:Hide() - + self.AutomaticTrackingCheckbox:SetValue(true) self.ManualTrackingCheckbox:SetValue(false) self.desc_label.text = self.LocTexts.AUTOMATIC_DESC - + elseif (newdb.aura_tracker.track_method == 0x2) then self.f_auto:Hide() self.f_manual:Show() - + self.AutomaticTrackingCheckbox:SetValue(false) self.ManualTrackingCheckbox:SetValue(true) self.desc_label.text = self.LocTexts.MANUAL_DESC @@ -193,653 +198,595 @@ local on_profile_changed = function(self, newdb) end local aura_panel_defaultoptions = { - height = 400, + height = 400, row_height = 18, width = 230, button_text_template = "OPTIONS_FONT_TEMPLATE" } -function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, texts) - +function DF:CreateAuraConfigPanel(parent, name, db, changeCallback, options, texts) local options_text_template = DF:GetTemplate("font", "OPTIONS_FONT_TEMPLATE") local options_dropdown_template = DF:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE") local options_switch_template = DF:GetTemplate("switch", "OPTIONS_CHECKBOX_TEMPLATE") local options_slider_template = DF:GetTemplate("slider", "OPTIONS_SLIDER_TEMPLATE") local options_button_template = DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE") - - local f = CreateFrame("frame", name, parent, "BackdropTemplate") - f.db = db - f.OnProfileChanged = on_profile_changed - f.LocTexts = texts + + local newAuraPanel = CreateFrame("frame", name, parent, "BackdropTemplate") + newAuraPanel.db = db + newAuraPanel.OnProfileChanged = onProfileChangedCallback + newAuraPanel.LocTexts = texts options = options or {} self.table.deploy(options, aura_panel_defaultoptions) - - local f_auto = CreateFrame("frame", "$parent_Automatic", f, "BackdropTemplate") - local f_manual = CreateFrame("frame", "$parent_Manual", f, "BackdropTemplate") - f_auto:SetPoint("topleft", f, "topleft", 0, -24) - f_manual:SetPoint("topleft", f, "topleft", 0, -24) - f_auto:SetSize(600, 600) - f_manual:SetSize(600, 600) - f.f_auto = f_auto - f.f_manual = f_manual - + + local auraPanel_Auto = CreateFrame("frame", "$parent_Automatic", newAuraPanel, "BackdropTemplate") + local auraPanel_Manual = CreateFrame("frame", "$parent_Manual", newAuraPanel, "BackdropTemplate") + auraPanel_Auto:SetPoint("topleft", newAuraPanel, "topleft", 0, -24) + auraPanel_Manual:SetPoint("topleft", newAuraPanel, "topleft", 0, -24) + auraPanel_Auto:SetSize(600, 600) + auraPanel_Manual:SetSize(600, 600) + newAuraPanel.f_auto = auraPanel_Auto + newAuraPanel.f_manual = auraPanel_Manual + --check if the texts table is valid and also deploy default values into the table in case some value is nil - texts = (type(texts == "table") and texts) or default_text_for_aura_frame - DF.table.deploy(texts, default_text_for_aura_frame) - - ------------- - - local on_switch_tracking_method = function(self) + texts = (type(texts == "table") and texts) or defaultTextForAuraFrame + DF.table.deploy(texts, defaultTextForAuraFrame) + + local onSwitchTrackingMethod = function(self) local method = self.Method - - f.db.aura_tracker.track_method = method - if (change_callback) then - DF:QuickDispatch(change_callback) + + newAuraPanel.db.aura_tracker.track_method = method + if (changeCallback) then + DF:QuickDispatch(changeCallback) end if (method == 0x1) then - f_auto:Show() - f_manual:Hide() - f.AutomaticTrackingCheckbox:SetValue(true) - f.ManualTrackingCheckbox:SetValue(false) - f.desc_label.text = texts.AUTOMATIC_DESC - + auraPanel_Auto:Show() + auraPanel_Manual:Hide() + newAuraPanel.AutomaticTrackingCheckbox:SetValue(true) + newAuraPanel.ManualTrackingCheckbox:SetValue(false) + newAuraPanel.desc_label.text = texts.AUTOMATIC_DESC + elseif (method == 0x2) then - f_auto:Hide() - f_manual:Show() - f.AutomaticTrackingCheckbox:SetValue(false) - f.ManualTrackingCheckbox:SetValue(true) - f.desc_label.text = texts.MANUAL_DESC + auraPanel_Auto:Hide() + auraPanel_Manual:Show() + newAuraPanel.AutomaticTrackingCheckbox:SetValue(false) + newAuraPanel.ManualTrackingCheckbox:SetValue(true) + newAuraPanel.desc_label.text = texts.MANUAL_DESC end end - - local background_method_selection = CreateFrame("frame", nil, f, "BackdropTemplate") - background_method_selection:SetHeight(82) - background_method_selection:SetPoint("topleft", f, "topleft", 0, 0) - background_method_selection:SetPoint("topright", f, "topright", 0, 0) - DF:ApplyStandardBackdrop(background_method_selection) - - local tracking_method_label = self:CreateLabel(background_method_selection, texts.METHOD, 12, "orange") - tracking_method_label:SetPoint("topleft", background_method_selection, "topleft", 6, -4) - - f.desc_label = self:CreateLabel(background_method_selection, "", 10, "silver") - f.desc_label:SetPoint("left", background_method_selection, "left", 130, 0) - f.desc_label:SetJustifyV ("top") - - local automatic_tracking_checkbox = DF:CreateSwitch(background_method_selection, on_switch_tracking_method, f.db.aura_tracker.track_method == 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, DF:GetTemplate("switch", "OPTIONS_CHECKBOX_BRIGHT_TEMPLATE")) - automatic_tracking_checkbox.Method = 0x1 - automatic_tracking_checkbox:SetAsCheckBox() - automatic_tracking_checkbox:SetSize(24, 24) - f.AutomaticTrackingCheckbox = automatic_tracking_checkbox - - local automatic_tracking_label = DF:CreateLabel(background_method_selection, "Automatic") - automatic_tracking_label:SetPoint("left", automatic_tracking_checkbox, "right", 2, 0) - - local manual_tracking_checkbox = DF:CreateSwitch(background_method_selection, on_switch_tracking_method, f.db.aura_tracker.track_method == 0x2, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, DF:GetTemplate("switch", "OPTIONS_CHECKBOX_BRIGHT_TEMPLATE")) - manual_tracking_checkbox.Method = 0x2 - manual_tracking_checkbox:SetAsCheckBox() - manual_tracking_checkbox:SetSize(24, 24) - f.ManualTrackingCheckbox = manual_tracking_checkbox - - local manual_tracking_label = DF:CreateLabel(background_method_selection, "Manual") - manual_tracking_label:SetPoint("left", manual_tracking_checkbox, "right", 2, 0) - - automatic_tracking_checkbox:SetPoint("topleft", tracking_method_label, "bottomleft", 0, -6) - manual_tracking_checkbox:SetPoint("topleft", automatic_tracking_checkbox, "bottomleft", 0, -6) - + + local methodSelectionBackground = CreateFrame("frame", nil, newAuraPanel, "BackdropTemplate") + methodSelectionBackground:SetHeight(82) + methodSelectionBackground:SetPoint("topleft", newAuraPanel, "topleft", 0, 0) + methodSelectionBackground:SetPoint("topright", newAuraPanel, "topright", 0, 0) + DF:ApplyStandardBackdrop(methodSelectionBackground) + + local trackingMethodLabel = self:CreateLabel(methodSelectionBackground, texts.METHOD, 12, "orange") + trackingMethodLabel:SetPoint("topleft", methodSelectionBackground, "topleft", 6, -4) + + newAuraPanel.desc_label = self:CreateLabel(methodSelectionBackground, "", 10, "silver") + newAuraPanel.desc_label:SetPoint("left", methodSelectionBackground, "left", 130, 0) + newAuraPanel.desc_label:SetJustifyV("top") + + local automaticTrackingCheckbox = DF:CreateSwitch(methodSelectionBackground, onSwitchTrackingMethod, newAuraPanel.db.aura_tracker.track_method == 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, DF:GetTemplate("switch", "OPTIONS_CHECKBOX_BRIGHT_TEMPLATE")) + automaticTrackingCheckbox.Method = 0x1 + automaticTrackingCheckbox:SetAsCheckBox() + automaticTrackingCheckbox:SetSize(24, 24) + newAuraPanel.AutomaticTrackingCheckbox = automaticTrackingCheckbox + + local automaticTrackingLabel = DF:CreateLabel(methodSelectionBackground, "Automatic") + automaticTrackingLabel:SetPoint("left", automaticTrackingCheckbox, "right", 2, 0) + + local manualTrackingCheckbox = DF:CreateSwitch(methodSelectionBackground, onSwitchTrackingMethod, newAuraPanel.db.aura_tracker.track_method == 0x2, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, DF:GetTemplate("switch", "OPTIONS_CHECKBOX_BRIGHT_TEMPLATE")) + manualTrackingCheckbox.Method = 0x2 + manualTrackingCheckbox:SetAsCheckBox() + manualTrackingCheckbox:SetSize(24, 24) + newAuraPanel.ManualTrackingCheckbox = manualTrackingCheckbox + + local manualTrackingLabel = DF:CreateLabel(methodSelectionBackground, "Manual") + manualTrackingLabel:SetPoint("left", manualTrackingCheckbox, "right", 2, 0) + + automaticTrackingCheckbox:SetPoint("topleft", trackingMethodLabel, "bottomleft", 0, -6) + manualTrackingCheckbox:SetPoint("topleft", automaticTrackingCheckbox, "bottomleft", 0, -6) + -------- anchors points local y = -110 -------- automatic - - --manual add the buff and ebuff names - local AllSpellsMap = {} - local AllSpellNames = {} - - --store a table with spell name as key and in the value an index table with spell IDs - local AllSpellsSameName = {} - - local load_all_spells = function(self, capsule) - if (not next (AllSpellsMap)) then - DF:LoadAllSpells (AllSpellsMap, AllSpellNames, AllSpellsSameName) - - f_auto.AddBuffBlacklistTextBox.SpellAutoCompleteList = AllSpellNames - f_auto.AddDebuffBlacklistTextBox.SpellAutoCompleteList = AllSpellNames - f_auto.AddBuffTracklistTextBox.SpellAutoCompleteList = AllSpellNames - f_auto.AddDebuffTracklistTextBox.SpellAutoCompleteList = AllSpellNames - - f_manual.NewBuffTextBox.SpellAutoCompleteList = AllSpellNames - f_manual.NewDebuffTextBox.SpellAutoCompleteList = AllSpellNames - - -- - - f_auto.AddBuffBlacklistTextBox:SetAsAutoComplete ("SpellAutoCompleteList") - f_auto.AddDebuffBlacklistTextBox:SetAsAutoComplete ("SpellAutoCompleteList") - f_auto.AddBuffTracklistTextBox:SetAsAutoComplete ("SpellAutoCompleteList") - f_auto.AddDebuffTracklistTextBox:SetAsAutoComplete ("SpellAutoCompleteList") - - f_manual.NewBuffTextBox:SetAsAutoComplete ("SpellAutoCompleteList") - f_manual.NewDebuffTextBox:SetAsAutoComplete ("SpellAutoCompleteList") - - -- - - f_auto.AddBuffBlacklistTextBox.ShouldOptimizeAutoComplete = true - f_auto.AddDebuffBlacklistTextBox.ShouldOptimizeAutoComplete = true - f_auto.AddBuffTracklistTextBox.ShouldOptimizeAutoComplete = true - f_auto.AddDebuffTracklistTextBox.ShouldOptimizeAutoComplete = true - - f_manual.NewBuffTextBox.ShouldOptimizeAutoComplete = true - f_manual.NewDebuffTextBox.ShouldOptimizeAutoComplete = true + + local setAutoCompleteWordList = function(self, capsule) + if (next(spellsHashMap)) then --this will error if the spell cache isn't loaded with DF:LoadSpellCache(hashMap, indexTable, allSpellsSameName) + auraPanel_Auto.AddBuffBlacklistTextBox.SpellAutoCompleteList = spellsIndexTable + auraPanel_Auto.AddDebuffBlacklistTextBox.SpellAutoCompleteList = spellsIndexTable + auraPanel_Auto.AddBuffTracklistTextBox.SpellAutoCompleteList = spellsIndexTable + auraPanel_Auto.AddDebuffTracklistTextBox.SpellAutoCompleteList = spellsIndexTable + + auraPanel_Manual.NewBuffTextBox.SpellAutoCompleteList = spellsIndexTable + auraPanel_Manual.NewDebuffTextBox.SpellAutoCompleteList = spellsIndexTable + + auraPanel_Auto.AddBuffBlacklistTextBox:SetAsAutoComplete("SpellAutoCompleteList") + auraPanel_Auto.AddDebuffBlacklistTextBox:SetAsAutoComplete("SpellAutoCompleteList") + auraPanel_Auto.AddBuffTracklistTextBox:SetAsAutoComplete("SpellAutoCompleteList") + auraPanel_Auto.AddDebuffTracklistTextBox:SetAsAutoComplete("SpellAutoCompleteList") + + auraPanel_Manual.NewBuffTextBox:SetAsAutoComplete("SpellAutoCompleteList") + auraPanel_Manual.NewDebuffTextBox:SetAsAutoComplete("SpellAutoCompleteList") + + auraPanel_Auto.AddBuffBlacklistTextBox.ShouldOptimizeAutoComplete = true + auraPanel_Auto.AddDebuffBlacklistTextBox.ShouldOptimizeAutoComplete = true + auraPanel_Auto.AddBuffTracklistTextBox.ShouldOptimizeAutoComplete = true + auraPanel_Auto.AddDebuffTracklistTextBox.ShouldOptimizeAutoComplete = true + + auraPanel_Manual.NewBuffTextBox.ShouldOptimizeAutoComplete = true + auraPanel_Manual.NewDebuffTextBox.ShouldOptimizeAutoComplete = true end end - + --this set the width of the background box, text entry and button local textEntryWidth = 120 --create the background - local background_add_blacklist = CreateFrame("frame", nil, f_auto, "BackdropTemplate") - background_add_blacklist:SetSize(textEntryWidth + 10, 135) - DF:ApplyStandardBackdrop(background_add_blacklist) - background_add_blacklist.__background:SetVertexColor(0.47, 0.27, 0.27) - - local background_add_tracklist = CreateFrame("frame", nil, f_auto, "BackdropTemplate") - background_add_tracklist:SetSize(textEntryWidth + 10, 135) - DF:ApplyStandardBackdrop(background_add_tracklist) - background_add_tracklist.__background:SetVertexColor(0.27, 0.27, 0.47) - + local blacklistAddBackground = CreateFrame("frame", nil, auraPanel_Auto, "BackdropTemplate") + blacklistAddBackground:SetSize(textEntryWidth + 10, 135) + DF:ApplyStandardBackdrop(blacklistAddBackground) + blacklistAddBackground.__background:SetVertexColor(0.47, 0.27, 0.27) + + local tracklistAddBackground = CreateFrame("frame", nil, auraPanel_Auto, "BackdropTemplate") + tracklistAddBackground:SetSize(textEntryWidth + 10, 135) + DF:ApplyStandardBackdrop(tracklistAddBackground) + tracklistAddBackground.__background:SetVertexColor(0.27, 0.27, 0.47) + --black list --create labels - local buff_blacklist_label = self:CreateLabel(background_add_blacklist, texts.MANUAL_ADD_BLACKLIST_BUFF, DF:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) - local debuff_blacklist_label = self:CreateLabel(background_add_blacklist, texts.MANUAL_ADD_BLACKLIST_DEBUFF, DF:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) - - local buff_name_blacklist_entry = self:CreateTextEntry(background_add_blacklist, function()end, textEntryWidth, 20, "AddBuffBlacklistTextBox", _, _, options_dropdown_template) - buff_name_blacklist_entry:SetHook("OnEditFocusGained", load_all_spells) - buff_name_blacklist_entry:SetJustifyH("left") - buff_name_blacklist_entry.tooltip = "Enter the buff name using lower case letters." - f_auto.AddBuffBlacklistTextBox = buff_name_blacklist_entry - - local debuff_name_blacklist_entry = self:CreateTextEntry(background_add_blacklist, function()end, textEntryWidth, 20, "AddDebuffBlacklistTextBox", _, _, options_dropdown_template) - debuff_name_blacklist_entry:SetHook("OnEditFocusGained", load_all_spells) - debuff_name_blacklist_entry:SetJustifyH("left") - debuff_name_blacklist_entry.tooltip = "Enter the debuff name using lower case letters." - f_auto.AddDebuffBlacklistTextBox = debuff_name_blacklist_entry - - local same_name_spells_add = function(spellID, t) - local spellName = GetSpellInfo(spellID) - if (spellName) then - if (not next (AllSpellsMap)) then - load_all_spells() - end - - spellName = lower (spellName) - local spellWithSameName = AllSpellsSameName [spellName] - if (spellWithSameName) then - if (t) then - t [spellName] = DF.table.copy({}, spellWithSameName) + local buffBlacklistLabel = self:CreateLabel(blacklistAddBackground, texts.MANUAL_ADD_BLACKLIST_BUFF, DF:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) + local debuffBlacklistLabel = self:CreateLabel(blacklistAddBackground, texts.MANUAL_ADD_BLACKLIST_DEBUFF, DF:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) + + local buffNameBlacklistEntry = self:CreateTextEntry(blacklistAddBackground, function()end, textEntryWidth, 20, "AddBuffBlacklistTextBox", _, _, options_dropdown_template) + buffNameBlacklistEntry:SetHook("OnEditFocusGained", setAutoCompleteWordList) + buffNameBlacklistEntry:SetJustifyH("left") + buffNameBlacklistEntry.tooltip = "Enter the buff name using lower case letters." + auraPanel_Auto.AddBuffBlacklistTextBox = buffNameBlacklistEntry + + local debuffNameBlacklistEntry = self:CreateTextEntry(blacklistAddBackground, function()end, textEntryWidth, 20, "AddDebuffBlacklistTextBox", _, _, options_dropdown_template) + debuffNameBlacklistEntry:SetHook("OnEditFocusGained", setAutoCompleteWordList) + debuffNameBlacklistEntry:SetJustifyH("left") + debuffNameBlacklistEntry.tooltip = "Enter the debuff name using lower case letters." + auraPanel_Auto.AddDebuffBlacklistTextBox = debuffNameBlacklistEntry + + local getSpellIDFromSpellName = function(spellName) + --check if the user entered a spell ID + local bIsSpellId = tonumber(spellName) + if (bIsSpellId) then + local spellId = tonumber(spellName) + if (spellId and spellId > 1 and spellId < 10000000) then + local isValidSpellID = GetSpellInfo(spellId) + if (isValidSpellID) then + return spellId else - db.aura_cache_by_name [spellName] = DF.table.copy({}, spellWithSameName) + return end end end - end - DF.AddSpellWithSameName = same_name_spells_add - - local get_spellID_from_string = function(text) - --check if the user entered a spell ID - local isSpellID = tonumber(text) - if (isSpellID and isSpellID > 1 and isSpellID < 10000000) then - local isValidSpellID = GetSpellInfo(isSpellID) - if (isValidSpellID) then - return isSpellID - else - return - end - end - + --get the spell ID from the spell name - text = lower (text) - local spellID = AllSpellsMap [text] - if (not spellID) then - return - end - - return spellID + spellName = lower(spellName) + return spellsHashMap[spellName] end - - local add_blacklist_buff_button = self:CreateButton(background_add_blacklist, function() - local text = buff_name_blacklist_entry.text - buff_name_blacklist_entry:SetText("") - buff_name_blacklist_entry:ClearFocus() - + + local addBuffNameToBacklistButton = self:CreateButton(blacklistAddBackground, function() + local text = buffNameBlacklistEntry.text + buffNameBlacklistEntry:SetText("") + buffNameBlacklistEntry:ClearFocus() + if (text ~= "") then --get the spellId - local spellId = get_spellID_from_string (text) + local spellId = getSpellIDFromSpellName(text) if (not spellId) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Spell not found!") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Spell not found!") return end - + --add the spellName to the blacklist - f.db.aura_tracker.buff_banned [spellId] = true - + newAuraPanel.db.aura_tracker.buff_banned [spellId] = true + --refresh the buff blacklist frame - f.buff_ignored:DoRefresh() + newAuraPanel.buff_ignored:Refresh() - DF:QuickDispatch(change_callback) - - --add to spells with the same name cache - same_name_spells_add (spellId) + DF:QuickDispatch(changeCallback) end - + end, textEntryWidth/2 -3, 20, "By Name", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), DF:GetTemplate("font", options.button_text_template)) - - local add_blacklist_buff_button_id = self:CreateButton(background_add_blacklist, function() - local text = buff_name_blacklist_entry.text - buff_name_blacklist_entry:SetText("") - buff_name_blacklist_entry:ClearFocus() - + + local addBuffIDToBacklistButton = self:CreateButton(blacklistAddBackground, function() + local text = buffNameBlacklistEntry.text + buffNameBlacklistEntry:SetText("") + buffNameBlacklistEntry:ClearFocus() + if (text ~= "") then if (not tonumber(text)) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Invalid Spell-ID.") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Invalid Spell-ID.") end - + --get the spellId - local spellId = get_spellID_from_string (text) + local spellId = getSpellIDFromSpellName(text) if (not spellId) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Spell not found!") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Spell not found!") return end - + --add the spellId to the blacklist - f.db.aura_tracker.buff_banned [spellId] = false - + newAuraPanel.db.aura_tracker.buff_banned [spellId] = false + --refresh the buff blacklist frame - f.buff_ignored:DoRefresh() + newAuraPanel.buff_ignored:Refresh() - DF:QuickDispatch(change_callback) - - --add to spells with the same name cache - same_name_spells_add (spellId) + DF:QuickDispatch(changeCallback) end - + end, textEntryWidth/2 -3, 20, "By ID", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), DF:GetTemplate("font", options.button_text_template)) - - local add_blacklist_debuff_button = self:CreateButton(background_add_blacklist, function() - local text = debuff_name_blacklist_entry.text - debuff_name_blacklist_entry:SetText("") - debuff_name_blacklist_entry:ClearFocus() - + + local addDebuffNameToBacklistButton = self:CreateButton(blacklistAddBackground, function() + local text = debuffNameBlacklistEntry.text + debuffNameBlacklistEntry:SetText("") + debuffNameBlacklistEntry:ClearFocus() + if (text ~= "") then --get the spellId - local spellId = get_spellID_from_string (text) + local spellId = getSpellIDFromSpellName(text) if (not spellId) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Spell not found!") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Spell not found!") return end - + --add the spellName to the blacklist - f.db.aura_tracker.debuff_banned [spellId] = true - + newAuraPanel.db.aura_tracker.debuff_banned [spellId] = true + --refresh the buff blacklist frame - f.debuff_ignored:DoRefresh() - - DF:QuickDispatch(change_callback) - - --add to spells with the same name cache - same_name_spells_add (spellId) + newAuraPanel.debuff_ignored:Refresh() + + DF:QuickDispatch(changeCallback) end end, textEntryWidth/2 -3, 20, "By Name", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), DF:GetTemplate("font", options.button_text_template)) - - local add_blacklist_debuff_button_id = self:CreateButton(background_add_blacklist, function() - local text = debuff_name_blacklist_entry.text - debuff_name_blacklist_entry:SetText("") - debuff_name_blacklist_entry:ClearFocus() - + + local addDebuffIDToBacklistButton = self:CreateButton(blacklistAddBackground, function() + local text = debuffNameBlacklistEntry.text + debuffNameBlacklistEntry:SetText("") + debuffNameBlacklistEntry:ClearFocus() + if (text ~= "") then if (not tonumber(text)) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Invalid Spell-ID.") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Invalid Spell-ID.") end - + --get the spellId - local spellId = get_spellID_from_string (text) + local spellId = getSpellIDFromSpellName(text) if (not spellId) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Spell not found!") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Spell not found!") return end - + --add the spellId to the blacklist - f.db.aura_tracker.debuff_banned [spellId] = false - + newAuraPanel.db.aura_tracker.debuff_banned [spellId] = false + --refresh the buff blacklist frame - f.debuff_ignored:DoRefresh() - - DF:QuickDispatch(change_callback) - - --add to spells with the same name cache - same_name_spells_add (spellId) + newAuraPanel.debuff_ignored:Refresh() + + DF:QuickDispatch(changeCallback) end - end, textEntryWidth/2 -3, 20, "By ID", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), DF:GetTemplate("font", options.button_text_template)) - - + end, textEntryWidth/2 -3, 20, "By ID", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), DF:GetTemplate("font", options.button_text_template)) + + --track list - local buff_tracklist_label = self:CreateLabel(background_add_tracklist, texts.MANUAL_ADD_TRACKLIST_BUFF, DF:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) - local debuff_tracklist_label = self:CreateLabel(background_add_tracklist, texts.MANUAL_ADD_TRACKLIST_DEBUFF, DF:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) - - local buff_name_tracklist_entry = self:CreateTextEntry(background_add_tracklist, function()end, textEntryWidth, 20, "AddBuffTracklistTextBox", _, _, options_dropdown_template) - buff_name_tracklist_entry:SetHook("OnEditFocusGained", load_all_spells) - buff_name_tracklist_entry:SetJustifyH("left") - buff_name_tracklist_entry.tooltip = "Enter the buff name using lower case letters." - f_auto.AddBuffTracklistTextBox = buff_name_tracklist_entry - - local debuff_name_tracklist_entry = self:CreateTextEntry(background_add_tracklist, function()end, textEntryWidth, 20, "AddDebuffTracklistTextBox", _, _, options_dropdown_template) - debuff_name_tracklist_entry:SetHook("OnEditFocusGained", load_all_spells) - debuff_name_tracklist_entry:SetJustifyH("left") - debuff_name_tracklist_entry.tooltip = "Enter the debuff name using lower case letters." - f_auto.AddDebuffTracklistTextBox = debuff_name_tracklist_entry - - local add_tracklist_debuff_button = self:CreateButton(background_add_tracklist, function() - local text = debuff_name_tracklist_entry.text - debuff_name_tracklist_entry:SetText("") - debuff_name_tracklist_entry:ClearFocus() - + local buffTracklistLabel = self:CreateLabel(tracklistAddBackground, texts.MANUAL_ADD_TRACKLIST_BUFF, DF:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) + local debuffTracklistLabel = self:CreateLabel(tracklistAddBackground, texts.MANUAL_ADD_TRACKLIST_DEBUFF, DF:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) + + local buffNameTracklistEntry = self:CreateTextEntry(tracklistAddBackground, function()end, textEntryWidth, 20, "AddBuffTracklistTextBox", _, _, options_dropdown_template) + buffNameTracklistEntry:SetHook("OnEditFocusGained", setAutoCompleteWordList) + buffNameTracklistEntry:SetJustifyH("left") + buffNameTracklistEntry.tooltip = "Enter the buff name using lower case letters." + auraPanel_Auto.AddBuffTracklistTextBox = buffNameTracklistEntry + + local debuffNameTracklistEntry = self:CreateTextEntry(tracklistAddBackground, function()end, textEntryWidth, 20, "AddDebuffTracklistTextBox", _, _, options_dropdown_template) + debuffNameTracklistEntry:SetHook("OnEditFocusGained", setAutoCompleteWordList) + debuffNameTracklistEntry:SetJustifyH("left") + debuffNameTracklistEntry.tooltip = "Enter the debuff name using lower case letters." + auraPanel_Auto.AddDebuffTracklistTextBox = debuffNameTracklistEntry + + local addDebuffNameToTracklistButton = self:CreateButton(tracklistAddBackground, function() + local text = debuffNameTracklistEntry.text + debuffNameTracklistEntry:SetText("") + debuffNameTracklistEntry:ClearFocus() + if (text ~= "") then --get the spellId - local spellId = get_spellID_from_string (text) + local spellId = getSpellIDFromSpellName(text) if (not spellId) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Spell not found!") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Spell not found!") return end - + --add the spellName to the tracklist - f.db.aura_tracker.debuff_tracked [spellId] = true - + newAuraPanel.db.aura_tracker.debuff_tracked [spellId] = true + --refresh the buff blacklist frame - f.debuff_tracked:DoRefresh() - - DF:QuickDispatch(change_callback) - - --add to spells with the same name cache - same_name_spells_add (spellId) + newAuraPanel.debuff_tracked:Refresh() + + DF:QuickDispatch(changeCallback) end end, textEntryWidth/2 -3, 20, "By Name", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), DF:GetTemplate("font", options.button_text_template)) - - local add_tracklist_debuff_button_id = self:CreateButton(background_add_tracklist, function() - local text = debuff_name_tracklist_entry.text - debuff_name_tracklist_entry:SetText("") - debuff_name_tracklist_entry:ClearFocus() - + + local addDebuffIDToTracklistButton = self:CreateButton(tracklistAddBackground, function() + local text = debuffNameTracklistEntry.text + debuffNameTracklistEntry:SetText("") + debuffNameTracklistEntry:ClearFocus() + if (text ~= "") then if (not tonumber(text)) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Invalid Spell-ID.") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Invalid Spell-ID.") end - + --get the spellId - local spellId = get_spellID_from_string (text) + local spellId = getSpellIDFromSpellName(text) if (not spellId) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Spell not found!") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Spell not found!") return end - - f.db.aura_tracker.debuff_tracked [spellId] = false - + + newAuraPanel.db.aura_tracker.debuff_tracked [spellId] = false + --refresh the buff blacklist frame - f.debuff_tracked:DoRefresh() - - DF:QuickDispatch(change_callback) - - --add to spells with the same name cache - same_name_spells_add (spellId) + newAuraPanel.debuff_tracked:Refresh() + + DF:QuickDispatch(changeCallback) end end, textEntryWidth/2 -3, 20, "By ID", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), DF:GetTemplate("font", options.button_text_template)) - - local add_tracklist_buff_button = self:CreateButton(background_add_tracklist, function() - local text = buff_name_tracklist_entry.text - buff_name_tracklist_entry:SetText("") - buff_name_tracklist_entry:ClearFocus() - + + local addBuffNameToTracklistButton = self:CreateButton(tracklistAddBackground, function() + local text = buffNameTracklistEntry.text + buffNameTracklistEntry:SetText("") + buffNameTracklistEntry:ClearFocus() + if (text ~= "") then --get the spellId - local spellId = get_spellID_from_string (text) + local spellId = getSpellIDFromSpellName(text) if (not spellId) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Spell not found!") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Spell not found!") return end - + --add the spellName to the tracklist - f.db.aura_tracker.buff_tracked [spellId] = true - + newAuraPanel.db.aura_tracker.buff_tracked [spellId] = true + --refresh the buff tracklist frame - f.buff_tracked:DoRefresh() + newAuraPanel.buff_tracked:Refresh() --callback the addon - DF:QuickDispatch(change_callback) - - --add to spells with the same name cache - same_name_spells_add (spellId) + DF:QuickDispatch(changeCallback) end - + end, textEntryWidth/2 -3, 20, "By Name", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), DF:GetTemplate("font", options.button_text_template)) - - local add_tracklist_buff_button_id = self:CreateButton(background_add_tracklist, function() - local text = buff_name_tracklist_entry.text - buff_name_tracklist_entry:SetText("") - buff_name_tracklist_entry:ClearFocus() - + + local addBuffIDToTracklistButton = self:CreateButton(tracklistAddBackground, function() + local text = buffNameTracklistEntry.text + buffNameTracklistEntry:SetText("") + buffNameTracklistEntry:ClearFocus() + if (text ~= "") then if (not tonumber(text)) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Invalid Spell-ID.") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Invalid Spell-ID.") end - + --get the spellId - local spellId = get_spellID_from_string (text) + local spellId = getSpellIDFromSpellName(text) if (not spellId) then - DetailsFramework.Msg ({__name = "DetailsFramework"}, "Spell not found!") + DetailsFramework.Msg({__name = "DetailsFramework"}, "Spell not found!") return end - + --add the spellId to the tracklist - f.db.aura_tracker.buff_tracked [spellId] = false - + newAuraPanel.db.aura_tracker.buff_tracked [spellId] = false + --refresh the buff tracklist frame - f.buff_tracked:DoRefresh() + newAuraPanel.buff_tracked:Refresh() --callback the addon - DF:QuickDispatch(change_callback) - - --add to spells with the same name cache - same_name_spells_add (spellId) + DF:QuickDispatch(changeCallback) end end, textEntryWidth/2 -3, 20, "By ID", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"), DF:GetTemplate("font", options.button_text_template)) - + --anchors: - background_add_blacklist:SetPoint("topleft", f_auto, "topleft", 0, y) - background_add_tracklist:SetPoint("topleft", background_add_blacklist, "bottomleft", 0, -10) - + blacklistAddBackground:SetPoint("topleft", auraPanel_Auto, "topleft", 0, y) + tracklistAddBackground:SetPoint("topleft", blacklistAddBackground, "bottomleft", 0, -10) + --debuff blacklist - debuff_name_blacklist_entry:SetPoint("topleft", background_add_blacklist, "topleft", 5, -20) - debuff_blacklist_label:SetPoint("bottomleft", debuff_name_blacklist_entry, "topleft", 0, 2) - add_blacklist_debuff_button:SetPoint("topleft", debuff_name_blacklist_entry, "bottomleft", 0, -2) - add_blacklist_debuff_button_id:SetPoint("left", add_blacklist_debuff_button, "right", 1, 0) - + debuffNameBlacklistEntry:SetPoint("topleft", blacklistAddBackground, "topleft", 5, -20) + debuffBlacklistLabel:SetPoint("bottomleft", debuffNameBlacklistEntry, "topleft", 0, 2) + addDebuffNameToBacklistButton:SetPoint("topleft", debuffNameBlacklistEntry, "bottomleft", 0, -2) + addDebuffIDToBacklistButton:SetPoint("left", addDebuffNameToBacklistButton, "right", 1, 0) + --buff blacklist - buff_blacklist_label:SetPoint("topleft", add_blacklist_debuff_button.widget, "bottomleft", 0, -10) - buff_name_blacklist_entry:SetPoint("topleft", buff_blacklist_label, "bottomleft", 0, -2) - add_blacklist_buff_button:SetPoint("topleft", buff_name_blacklist_entry, "bottomleft", 0, -2) - add_blacklist_buff_button_id:SetPoint("left", add_blacklist_buff_button, "right", 1, 0) + buffBlacklistLabel:SetPoint("topleft", addDebuffNameToBacklistButton.widget, "bottomleft", 0, -10) + buffNameBlacklistEntry:SetPoint("topleft", buffBlacklistLabel, "bottomleft", 0, -2) + addBuffNameToBacklistButton:SetPoint("topleft", buffNameBlacklistEntry, "bottomleft", 0, -2) + addBuffIDToBacklistButton:SetPoint("left", addBuffNameToBacklistButton, "right", 1, 0) - --debuff tracklist - debuff_name_tracklist_entry:SetPoint("topleft", background_add_tracklist, "topleft", 5, -20) - debuff_tracklist_label:SetPoint("bottomleft", debuff_name_tracklist_entry, "topleft", 0, 2) - add_tracklist_debuff_button:SetPoint("topleft", debuff_name_tracklist_entry, "bottomleft", 0, -2) - add_tracklist_debuff_button_id:SetPoint("left", add_tracklist_debuff_button, "right", 1, 0) - + debuffNameTracklistEntry:SetPoint("topleft", tracklistAddBackground, "topleft", 5, -20) + debuffTracklistLabel:SetPoint("bottomleft", debuffNameTracklistEntry, "topleft", 0, 2) + addDebuffNameToTracklistButton:SetPoint("topleft", debuffNameTracklistEntry, "bottomleft", 0, -2) + addDebuffIDToTracklistButton:SetPoint("left", addDebuffNameToTracklistButton, "right", 1, 0) + --buff tracklist - buff_tracklist_label:SetPoint("topleft", add_tracklist_debuff_button.widget, "bottomleft", 0, -10) - buff_name_tracklist_entry:SetPoint("topleft", buff_tracklist_label, "bottomleft", 0, -2) - add_tracklist_buff_button:SetPoint("topleft", buff_name_tracklist_entry, "bottomleft", 0, -2) - add_tracklist_buff_button_id:SetPoint("left", add_tracklist_buff_button, "right", 1, 0) - - local ALL_BUFFS = {} - local ALL_DEBUFFS = {} - + buffTracklistLabel:SetPoint("topleft", addDebuffNameToTracklistButton.widget, "bottomleft", 0, -10) + buffNameTracklistEntry:SetPoint("topleft", buffTracklistLabel, "bottomleft", 0, -2) + addBuffNameToTracklistButton:SetPoint("topleft", buffNameTracklistEntry, "bottomleft", 0, -2) + addBuffIDToTracklistButton:SetPoint("left", addBuffNameToTracklistButton, "right", 1, 0) + --options passed to the create aura panel local width, height, row_height = options.width, options.height, options.row_height - local autoTrackList_LineOnEnter = function(self, capsule, value) - + local scrollWidth = 208 + +do --deprecated, using a scrollbox tempate from scrollbox.lua + local scrollHeight = 343 + local lineAmount = 18 + local lineHeight = 18 + local backdropColor = {.8, .8, .8, 0.2} + local backdropColor_OnEnter = {.8, .8, .8, 0.4} + + --aura scroll box default settings + local auraScrollDefaultSettings = { + show_spell_tooltip = false, + line_height = 18, + line_amount = 18, + } + + local autoTrackList_LineOnEnter = function(self, capsule, value) local flag = self.Flag value = value or self.SpellID - + if not flag then GameCooltip2:Preset(2) GameCooltip2:SetOwner(self, "left", "right", 2, 0) GameCooltip2:SetOption("TextSize", 10) - + local spellName, _, spellIcon = GetSpellInfo(value) if (spellName) then - GameCooltip2:AddLine(spellName .. " (" .. value .. ")") - GameCooltip2:AddIcon (spellIcon, 1, 1, 14, 14, .1, .9, .1, .9) + GameCooltip2:AddLine(spellName .. "(" .. value .. ")") + GameCooltip2:AddIcon(spellIcon, 1, 1, 14, 14, .1, .9, .1, .9) end GameCooltip2:Show() + else - - local spellName = GetSpellInfo(value) - if (spellName) then - - local spellsWithSameName = db.aura_cache_by_name [lower (spellName)] - if (not spellsWithSameName) then - same_name_spells_add (value) - spellsWithSameName = db.aura_cache_by_name [lower (spellName)] - end - - if (spellsWithSameName) then + local spellName, _, spellIcon = GetSpellInfo(value) + if (spellName and spellsWithSameName) then + local spellNameLower = spellName:lower() + local sameNameSpells = spellsWithSameName[spellNameLower] + + if (sameNameSpells) then GameCooltip2:Preset(2) GameCooltip2:SetOwner(self, "left", "right", 2, 0) GameCooltip2:SetOption("TextSize", 10) - - for i, spellID in ipairs(spellsWithSameName) do - local spellName, _, spellIcon = GetSpellInfo(spellID) - if (spellName) then - GameCooltip2:AddLine(spellName .. " (" .. spellID .. ")") - GameCooltip2:AddIcon (spellIcon, 1, 1, 14, 14, .1, .9, .1, .9) - end + + for i, spellId in ipairs(sameNameSpells) do + GameCooltip2:AddLine(spellName .. " (" .. spellId .. ")") + GameCooltip2:AddIcon(spellIcon, 1, 1, 14, 14, .1, .9, .1, .9) end - + GameCooltip2:Show() end end - end end - + local autoTrackList_LineOnLeave = function() GameCooltip2:Hide() end - - local scrollWidth = 208 - local scrollHeight = 343 - local lineAmount = 18 - local lineHeight = 18 - local backdropColor = {.8, .8, .8, 0.2} - local backdropColor_OnEnter = {.8, .8, .8, 0.4} - - local createAuraScrollBox = function(parent, name, member, title, db, removeFunc) - + + + local createAuraScrollBox = function(scrollBoxParent, scrollBoxName, scrollBoxParentKey, scrollBoxTitle, databaseTable, removeAuraFunc, options) + local scrollOptions = {} + detailsFramework.OptionsFunctions.BuildOptionsTable(scrollOptions, auraScrollDefaultSettings, options) + local updateFunc = function(self, data, offset, totalLines) for i = 1, totalLines do local index = i + offset - local auraTable = data [index] + local auraTable = data[index] if (auraTable) then - local line = self:GetLine (i) - local spellID, spellName, spellIcon, lowerSpellName, flag = unpack(auraTable) - - line.SpellID = spellID + local line = self:GetLine(i) + local spellId, spellName, spellIcon, lowerSpellName, flag = unpack(auraTable) + + line.SpellID = spellId line.SpellName = spellName line.SpellNameLower = lowerSpellName line.SpellIcon = spellIcon line.Flag = flag - - if flag then + + if (flag) then line.name:SetText(spellName) else - line.name:SetText(spellName .. " (" .. spellID .. ")") + line.name:SetText(spellName .. "(" .. spellId .. ")") end + line.icon:SetTexture(spellIcon) line.icon:SetTexCoord(.1, .9, .1, .9) end end end - - local lineOnEnter = function(self) - self:SetBackdropColor(unpack(backdropColor_OnEnter)) - - --GameTooltip:SetOwner(self, "ANCHOR_CURSOR") - --GameTooltip:SetSpellByID(self.SpellID) - --GameTooltip:AddLine(" ") - --GameTooltip:Show() + + local auraLineOnEnter = function(line) + if (scrollOptions.options.show_spell_tooltip and line.SpellID and GetSpellInfo(line.SpellID)) then + GameTooltip:SetOwner(line, "ANCHOR_CURSOR") + GameTooltip:SetSpellByID(line.SpellID) + GameTooltip:AddLine(" ") + GameTooltip:Show() + end + + line:SetBackdropColor(unpack(backdropColor_OnEnter)) end - - local lineOnLeave = function(self) + + local auraLineOnLeave = function(self) self:SetBackdropColor(unpack(backdropColor)) - --GameTooltip:Hide() + GameTooltip:Hide() end - - local onRemoveClick = function(self) - local spellID = self:GetParent().SpellID - db [spellID] = nil - db ["".. (spellID or "")] = nil -- cleanup... - parent [member]:DoRefresh() - if (removeFunc) then - DF:QuickDispatch(removeFunc) + + local onAuraRemoveButtonClick = function(self) + local spellId = self:GetParent().SpellID + databaseTable[spellId] = nil + databaseTable["" .. (spellId or "")] = nil -- cleanup... + scrollBoxParent[scrollBoxParentKey]:Refresh() + if (removeAuraFunc) then --upvalue + detailsFramework:QuickDispatch(removeAuraFunc) end end - + local createLineFunc = function(self, index) local line = CreateFrame("button", "$parentLine" .. index, self, "BackdropTemplate") - line:SetPoint("topleft", self, "topleft", 1, - ((index - 1) * (lineHeight + 1)) - 1) + local lineHeight = scrollOptions.options.line_height + + line:SetPoint("topleft", self, "topleft", 1, -((index - 1) * (lineHeight + 1)) - 1) line:SetSize(scrollWidth - 2, lineHeight) line:SetScript("OnEnter", autoTrackList_LineOnEnter) - line:HookScript ("OnEnter", lineOnEnter) + line:HookScript("OnEnter", auraLineOnEnter) line:SetScript("OnLeave", autoTrackList_LineOnLeave) - line:HookScript ("OnLeave", lineOnLeave) - + line:HookScript("OnLeave", auraLineOnLeave) + line:SetBackdrop({bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}) line:SetBackdropColor(unpack(backdropColor)) - + local icon = line:CreateTexture("$parentIcon", "overlay") icon:SetSize(lineHeight - 2, lineHeight - 2) - + local name = line:CreateFontString("$parentName", "overlay", "GameFontNormal") DF:SetFontSize(name, 10) - local remove_button = CreateFrame("button", "$parentRemoveButton", line, "UIPanelCloseButton") - remove_button:SetSize(16, 16) - remove_button:SetScript("OnClick", onRemoveClick) - remove_button:SetPoint("topright", line, "topright") - remove_button:GetNormalTexture():SetDesaturated(true) - + local removeButton = CreateFrame("button", "$parentRemoveButton", line, "UIPanelCloseButton") + removeButton:SetSize(16, 16) + removeButton:SetScript("OnClick", onAuraRemoveButtonClick) + removeButton:SetPoint("topright", line, "topright") + removeButton:GetNormalTexture():SetDesaturated(true) + icon:SetPoint("left", line, "left", 2, 0) name:SetPoint("left", icon, "right", 3, 0) - + line.icon = icon line.name = name - line.removebutton = remove_button - + line.removebutton = removeButton + return line end - local scroll = DF:CreateScrollBox (parent, name, updateFunc, db, scrollWidth, scrollHeight, lineAmount, lineHeight) - DF:ReskinSlider(scroll) - parent [member] = scroll - scroll.OriginalData = db - - function scroll:DoRefresh() + local auraScrollBox = DF:CreateScrollBox(scrollBoxParent, scrollBoxName, updateFunc, databaseTable, scrollWidth, scrollHeight, scrollOptions.options.line_amount, scrollOptions.options.line_height) + DF:ReskinSlider(auraScrollBox) + scrollBoxParent[scrollBoxParentKey] = auraScrollBox + auraScrollBox.OriginalData = databaseTable + + function auraScrollBox:Refresh() local t = {} local added = {} - for spellID, flag in pairs(scroll.OriginalData) do + for spellID, flag in pairs(auraScrollBox.OriginalData) do local spellName, _, spellIcon = GetSpellInfo(spellID) if (spellName and not added[tonumber(spellID) or 0]) then local lowerSpellName = spellName:lower() @@ -847,109 +794,102 @@ function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, t added[tonumber(spellID) or 0] = true end end - - table.sort (t, function(t1, t2) return t1[4] < t2[4] end) - - self:SetData (t) + + table.sort(t, function(t1, t2) return t1[4] < t2[4] end) + + self:SetData(t) self:Refresh() end - - function scroll:DoSetData (newDB) - self:SetData (newDB) - scroll.OriginalData = newDB - self:DoRefresh() + + function auraScrollBox:DoSetData(newDB) + self:SetData(newDB) + self.OriginalData = newDB + if (self.Refresh) then + self:Refresh() + else + self:Refresh() + end end - - local title = DF:CreateLabel(parent, title) - title.textcolor = "silver" - title.textsize = 10 - title:SetPoint("bottomleft", scroll, "topleft", 0, 2) + + local titleLabel = DF:CreateLabel(scrollBoxParent, scrollBoxTitle) + titleLabel.textcolor = "silver" + titleLabel.textsize = 10 + titleLabel:SetPoint("bottomleft", auraScrollBox, "topleft", 0, 2) for i = 1, lineAmount do - scroll:CreateLine (createLineFunc) + auraScrollBox:CreateLine(createLineFunc) end - - scroll:DoRefresh() - return scroll + + auraScrollBox:Refresh() + return auraScrollBox end - - local buff_tracked = createAuraScrollBox (f_auto, "$parentBuffTracked", "BuffTrackerScroll", f.LocTexts.BUFFS_TRACKED, f.db.aura_tracker.buff_tracked, function() - if (change_callback) then - DF:QuickDispatch(change_callback) - end - end) - local debuff_tracked = createAuraScrollBox (f_auto, "$parentDebuffTracked", "DebuffTrackerScroll", f.LocTexts.DEBUFFS_TRACKED, f.db.aura_tracker.debuff_tracked, function() - if (change_callback) then - DF:QuickDispatch(change_callback) - end - end) - - local buff_ignored = createAuraScrollBox (f_auto, "$parentBuffIgnored", "BuffIgnoredScroll", f.LocTexts.BUFFS_IGNORED, f.db.aura_tracker.buff_banned, function() - if (change_callback) then - DF:QuickDispatch(change_callback) - end - end) - local debuff_ignored = createAuraScrollBox (f_auto, "$parentDebuffIgnored", "DebuffIgnoredScroll", f.LocTexts.DEBUFFS_IGNORED, f.db.aura_tracker.debuff_banned, function() - if (change_callback) then - DF:QuickDispatch(change_callback) +end + + local onAuraRemoveCallback = function() + if (changeCallback) then + DF:QuickDispatch(changeCallback) end - end) - + end + + options.title_text = texts.DEBUFFS_TRACKED or defaultTextForAuraFrame.DEBUFFS_TRACKED + local debuffTrackedAuraScrollBox = detailsFramework:CreateAuraScrollBox(auraPanel_Auto, "$parentDebuffTracked", newAuraPanel.db.aura_tracker.debuff_tracked, onAuraRemoveCallback, options) + auraPanel_Auto.DebuffTrackerScroll = debuffTrackedAuraScrollBox + + options.title_text = texts.BUFFS_IGNORED or defaultTextForAuraFrame.BUFFS_IGNORED + local buffIgnoredAuraScrollBox = detailsFramework:CreateAuraScrollBox(auraPanel_Auto, "$parentBuffIgnored", newAuraPanel.db.aura_tracker.buff_banned, onAuraRemoveCallback, options) + auraPanel_Auto.BuffIgnoredScroll = buffIgnoredAuraScrollBox + + options.title_text = texts.DEBUFFS_IGNORED or defaultTextForAuraFrame.DEBUFFS_IGNORED + local debuffIgnoredAuraScrollBox = detailsFramework:CreateAuraScrollBox(auraPanel_Auto, "$parentDebuffIgnored", newAuraPanel.db.aura_tracker.debuff_banned, onAuraRemoveCallback, options) + auraPanel_Auto.DebuffIgnoredScroll = debuffIgnoredAuraScrollBox + + options.title_text = texts.BUFFS_TRACKED or defaultTextForAuraFrame.BUFFS_TRACKED + local buffTrackedAuraScrollBox = detailsFramework:CreateAuraScrollBox(auraPanel_Auto, "$parentBuffTracked", newAuraPanel.db.aura_tracker.buff_tracked, onAuraRemoveCallback, options) + auraPanel_Auto.BuffTrackerScroll = buffTrackedAuraScrollBox + local xLocation = 140 scrollWidth = scrollWidth + 20 - - debuff_ignored:SetPoint("topleft", f_auto, "topleft", 0 + xLocation, y) - buff_ignored:SetPoint("topleft", f_auto, "topleft", 8 + scrollWidth + xLocation, y) - debuff_tracked:SetPoint("topleft", f_auto, "topleft", 16 + (scrollWidth * 2) + xLocation, y) - buff_tracked:SetPoint("topleft", f_auto, "topleft", 24 + (scrollWidth * 3) + xLocation, y) - - f.buff_ignored = buff_ignored - f.debuff_ignored = debuff_ignored - f.buff_tracked = buff_tracked - f.debuff_tracked = debuff_tracked - - f_auto:SetScript("OnShow", function() - for i = 1, BUFF_MAX_DISPLAY do - local name, texture, count, debuffType, duration, expirationTime, caster, _, nameplateShowPersonal, spellId, _, _, _, nameplateShowAll = UnitAura ("player", i, "HELPFUL") - if (name) then - ALL_BUFFS [spellId] = true - end - local name, texture, count, debuffType, duration, expirationTime, caster, _, nameplateShowPersonal, spellId, _, _, _, nameplateShowAll = UnitAura ("player", i, "HARMFUL") - if (name) then - ALL_DEBUFFS [spellId] = true - end - end - - buff_tracked:DoRefresh() - debuff_tracked:DoRefresh() - buff_ignored:DoRefresh() - debuff_ignored:DoRefresh() - + + debuffIgnoredAuraScrollBox:SetPoint("topleft", auraPanel_Auto, "topleft", 0 + xLocation, y) + buffIgnoredAuraScrollBox:SetPoint("topleft", auraPanel_Auto, "topleft", 8 + scrollWidth + xLocation, y) + debuffTrackedAuraScrollBox:SetPoint("topleft", auraPanel_Auto, "topleft", 16 +(scrollWidth * 2) + xLocation, y) + buffTrackedAuraScrollBox:SetPoint("topleft", auraPanel_Auto, "topleft", 24 +(scrollWidth * 3) + xLocation, y) + + newAuraPanel.buff_ignored = buffIgnoredAuraScrollBox + newAuraPanel.debuff_ignored = debuffIgnoredAuraScrollBox + newAuraPanel.buff_tracked = buffTrackedAuraScrollBox + newAuraPanel.debuff_tracked = debuffTrackedAuraScrollBox + + auraPanel_Auto:SetScript("OnShow", function() + buffTrackedAuraScrollBox:Refresh() + debuffTrackedAuraScrollBox:Refresh() + buffIgnoredAuraScrollBox:Refresh() + debuffIgnoredAuraScrollBox:Refresh() end) - f_auto:SetScript("OnHide", function() + auraPanel_Auto:SetScript("OnHide", function() -- end) --show the frame selecton on the f.db - if (f.db.aura_tracker.track_method == 0x1) then - on_switch_tracking_method (automatic_tracking_checkbox) - elseif (f.db.aura_tracker.track_method == 0x2) then - on_switch_tracking_method (manual_tracking_checkbox) + if (newAuraPanel.db.aura_tracker.track_method == 0x1) then + onSwitchTrackingMethod(automaticTrackingCheckbox) + elseif (newAuraPanel.db.aura_tracker.track_method == 0x2) then + onSwitchTrackingMethod(manualTrackingCheckbox) end - + -------manual --build the two aura scrolls for buff and debuff - + local scroll_width = width local scroll_height = height local scroll_lines = 15 local scroll_line_height = 20 - + local backdrop_color = {.8, .8, .8, 0.2} local backdrop_color_on_enter = {.8, .8, .8, 0.4} - + local line_onenter = function(self) self:SetBackdropColor(unpack(backdrop_color_on_enter)) local spellid = select(7, GetSpellInfo(self.value)) @@ -960,39 +900,39 @@ function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, t GameTooltip:Show() end end - + local line_onleave = function(self) self:SetBackdropColor(unpack(backdrop_color)) GameTooltip:Hide() end - + local onclick_remove_button = function(self) local spell = self:GetParent().value local data = self:GetParent():GetParent():GetData() - + for i = 1, #data do if (data[i] == spell) then tremove(data, i) break end end - + self:GetParent():GetParent():Refresh() end - + local scroll_createline = function(self, index) local line = CreateFrame("button", "$parentLine" .. index, self, "BackdropTemplate") line:SetPoint("topleft", self, "topleft", 1, -((index-1)*(scroll_line_height+1)) - 1) line:SetSize(scroll_width - 2, scroll_line_height) line:SetScript("OnEnter", line_onenter) line:SetScript("OnLeave", line_onleave) - + line:SetBackdrop({bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}) line:SetBackdropColor(unpack(backdrop_color)) - + local icon = line:CreateTexture("$parentIcon", "overlay") icon:SetSize(scroll_line_height - 2, scroll_line_height - 2) - + local name = line:CreateFontString("$parentName", "overlay", "GameFontNormal") local remove_button = CreateFrame("button", "$parentRemoveButton", line, "UIPanelCloseButton") @@ -1000,14 +940,14 @@ function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, t remove_button:SetScript("OnClick", onclick_remove_button) remove_button:SetPoint("topright", line, "topright") remove_button:GetNormalTexture():SetDesaturated(true) - + icon:SetPoint("left", line, "left", 2, 0) name:SetPoint("left", icon, "right", 2, 0) - + line.icon = icon line.name = name line.removebutton = remove_button - + return line end @@ -1016,7 +956,7 @@ function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, t local index = i + offset local aura = data [index] if (aura) then - local line = self:GetLine (i) + local line = self:GetLine(i) local name, _, icon = GetSpellInfo(aura) line.value = aura if (name) then @@ -1030,65 +970,65 @@ function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, t end end end - - local buffs_added = self:CreateScrollBox (f_manual, "$parentBuffsAdded", scroll_refresh, f.db.aura_tracker.buff, scroll_width, scroll_height, scroll_lines, scroll_line_height) - buffs_added:SetPoint("topleft", f_manual, "topleft", 0, y) + + local buffs_added = self:CreateScrollBox(auraPanel_Manual, "$parentBuffsAdded", scroll_refresh, newAuraPanel.db.aura_tracker.buff, scroll_width, scroll_height, scroll_lines, scroll_line_height) + buffs_added:SetPoint("topleft", auraPanel_Manual, "topleft", 0, y) DF:ReskinSlider(buffs_added) - - for i = 1, scroll_lines do - buffs_added:CreateLine (scroll_createline) + + for i = 1, scroll_lines do + buffs_added:CreateLine(scroll_createline) end - - local debuffs_added = self:CreateScrollBox (f_manual, "$parentDebuffsAdded", scroll_refresh, f.db.aura_tracker.debuff, scroll_width, scroll_height, scroll_lines, scroll_line_height) - debuffs_added:SetPoint("topleft", f_manual, "topleft", width+30, y) + + local debuffs_added = self:CreateScrollBox(auraPanel_Manual, "$parentDebuffsAdded", scroll_refresh, newAuraPanel.db.aura_tracker.debuff, scroll_width, scroll_height, scroll_lines, scroll_line_height) + debuffs_added:SetPoint("topleft", auraPanel_Manual, "topleft", width+30, y) DF:ReskinSlider(debuffs_added) - - for i = 1, scroll_lines do - debuffs_added:CreateLine (scroll_createline) + + for i = 1, scroll_lines do + debuffs_added:CreateLine(scroll_createline) end - - f.buffs_added = buffs_added - f.debuffs_added = debuffs_added - + + newAuraPanel.buffs_added = buffs_added + newAuraPanel.debuffs_added = debuffs_added + local buffs_added_name = DF:CreateLabel(buffs_added, "Buffs", 12, "silver") buffs_added_name:SetTemplate(DF:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) buffs_added_name:SetPoint("bottomleft", buffs_added, "topleft", 0, 2) buffs_added.Title = buffs_added_name - + local debuffs_added_name = DF:CreateLabel(debuffs_added, "Debuffs", 12, "silver") debuffs_added_name:SetTemplate(DF:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) debuffs_added_name:SetPoint("bottomleft", debuffs_added, "topleft", 0, 2) debuffs_added.Title = debuffs_added_name - + -- build the text entry to type the spellname - local new_buff_string = self:CreateLabel(f_manual, "Add Buff") - local new_debuff_string = self:CreateLabel(f_manual, "Add Debuff") - local new_buff_entry = self:CreateTextEntry(f_manual, function()end, 200, 20, "NewBuffTextBox", _, _, options_dropdown_template) - local new_debuff_entry = self:CreateTextEntry(f_manual, function()end, 200, 20, "NewDebuffTextBox", _, _, options_dropdown_template) - - new_buff_entry:SetHook("OnEditFocusGained", load_all_spells) - new_debuff_entry:SetHook("OnEditFocusGained", load_all_spells) + local new_buff_string = self:CreateLabel(auraPanel_Manual, "Add Buff") + local new_debuff_string = self:CreateLabel(auraPanel_Manual, "Add Debuff") + local new_buff_entry = self:CreateTextEntry(auraPanel_Manual, function()end, 200, 20, "NewBuffTextBox", _, _, options_dropdown_template) + local new_debuff_entry = self:CreateTextEntry(auraPanel_Manual, function()end, 200, 20, "NewDebuffTextBox", _, _, options_dropdown_template) + + new_buff_entry:SetHook("OnEditFocusGained", setAutoCompleteWordList) + new_debuff_entry:SetHook("OnEditFocusGained", setAutoCompleteWordList) new_buff_entry.tooltip = "Enter the buff name using lower case letters.\n\nYou can add several spells at once using |cFFFFFF00;|r to separate each spell name." new_debuff_entry.tooltip = "Enter the debuff name using lower case letters.\n\nYou can add several spells at once using |cFFFFFF00;|r to separate each spell name." - + new_buff_entry:SetJustifyH("left") new_debuff_entry:SetJustifyH("left") - - local add_buff_button = self:CreateButton(f_manual, function() - + + local add_buff_button = self:CreateButton(auraPanel_Manual, function() + local text = new_buff_entry.text new_buff_entry:SetText("") new_buff_entry:ClearFocus() - + if (text ~= "") then --check for more than one spellname if (text:find(";")) then for _, spellName in ipairs({strsplit(";", text)}) do - spellName = self:trim (spellName) - local spellID = get_spellID_from_string (spellName) + spellName = self:trim(spellName) + local spellID = getSpellIDFromSpellName(spellName) if (spellID) then - tinsert(f.db.aura_tracker.buff, spellID) + tinsert(newAuraPanel.db.aura_tracker.buff, spellID) --[[ if not tonumber(spellName) then tinsert(f.db.aura_tracker.buff, spellName) @@ -1102,13 +1042,13 @@ function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, t end else --get the spellId - local spellID = get_spellID_from_string (text) + local spellID = getSpellIDFromSpellName(text) if (not spellID) then print("spellIs for spell ", text, "not found") return end - - tinsert(f.db.aura_tracker.buff, spellID) + + tinsert(newAuraPanel.db.aura_tracker.buff, spellID) --[[ if not tonumber(text) then tinsert(f.db.aura_tracker.buff, text) @@ -1117,13 +1057,13 @@ function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, t end ]]-- end - + buffs_added:Refresh() end - + end, 100, 20, "Add Buff", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE")) - - local add_debuff_button = self:CreateButton(f_manual, function() + + local add_debuff_button = self:CreateButton(auraPanel_Manual, function() local text = new_debuff_entry.text new_debuff_entry:SetText("") new_debuff_entry:ClearFocus() @@ -1131,11 +1071,11 @@ function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, t --check for more than one spellname if (text:find(";")) then for _, spellName in ipairs({strsplit(";", text)}) do - spellName = self:trim (spellName) - local spellID = get_spellID_from_string (spellName) - + spellName = self:trim(spellName) + local spellID = getSpellIDFromSpellName(spellName) + if (spellID) then - tinsert(f.db.aura_tracker.debuff, spellID) + tinsert(newAuraPanel.db.aura_tracker.debuff, spellID) --[[ if not tonumber(spellName) then tinsert(f.db.aura_tracker.debuff, spellName) @@ -1149,13 +1089,13 @@ function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, t end else --get the spellId - local spellID = get_spellID_from_string (text) + local spellID = getSpellIDFromSpellName(text) if (not spellID) then print("spellIs for spell ", text, "not found") return end - - tinsert(f.db.aura_tracker.debuff, spellID) + + tinsert(newAuraPanel.db.aura_tracker.debuff, spellID) --[[ if not tonumber(text) then print(text) @@ -1166,20 +1106,20 @@ function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, t end ]]-- end - + debuffs_added:Refresh() end end, 100, 20, "Add Debuff", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE")) - + local multiple_spells_label = DF:CreateLabel(buffs_added, "You can add multiple auras at once by separating them with ';'.\nExample: Fireball; Frostbolt; Flamestrike", 10, "gray") multiple_spells_label:SetSize(350, 24) - multiple_spells_label:SetJustifyV ("top") - - local export_box = self:CreateTextEntry(f_manual, function()end, 242, 20, "ExportAuraTextBox", _, _, options_dropdown_template) - - local export_buff_button = self:CreateButton(f_manual, function() + multiple_spells_label:SetJustifyV("top") + + local export_box = self:CreateTextEntry(auraPanel_Manual, function()end, 242, 20, "ExportAuraTextBox", _, _, options_dropdown_template) + + local export_buff_button = self:CreateButton(auraPanel_Manual, function() local str = "" - for _, spellId in ipairs(f.db.aura_tracker.buff) do + for _, spellId in ipairs(newAuraPanel.db.aura_tracker.buff) do local spellName = GetSpellInfo(spellId) if (spellName) then str = str .. spellName .. "; " @@ -1188,95 +1128,76 @@ function DF:CreateAuraConfigPanel (parent, name, db, change_callback, options, t export_box.text = str export_box:SetFocus(true) export_box:HighlightText() - + end, 120, 20, "Export Buffs", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE")) - - local export_debuff_button = self:CreateButton(f_manual, function() + + local export_debuff_button = self:CreateButton(auraPanel_Manual, function() local str = "" - for _, spellId in ipairs(f.db.aura_tracker.debuff) do + for _, spellId in ipairs(newAuraPanel.db.aura_tracker.debuff) do local spellName = GetSpellInfo(spellId) if (spellName) then str = str .. spellName .. "; " end end - + export_box.text = str export_box:SetFocus(true) export_box:HighlightText() - + end, 120, 20, "Export Debuffs", nil, nil, nil, nil, nil, nil, DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE")) - - new_buff_entry:SetPoint("topleft", f_manual, "topleft", 480, y) + + new_buff_entry:SetPoint("topleft", auraPanel_Manual, "topleft", 480, y) new_buff_string:SetPoint("bottomleft", new_buff_entry, "topleft", 0, 2) add_buff_button:SetPoint("left", new_buff_entry, "right", 2, 0) add_buff_button.tooltip = "Add the aura to be tracked.\n\nClick an aura on the list to remove it." - + new_debuff_string:SetPoint("topleft", new_buff_entry, "bottomleft", 0, -6) new_debuff_entry:SetPoint("topleft", new_debuff_string, "bottomleft", 0, -2) add_debuff_button:SetPoint("left", new_debuff_entry, "right", 2, 0) add_debuff_button.tooltip = "Add the aura to be tracked.\n\nClick an aura on the list to remove it." multiple_spells_label:SetPoint("topleft", new_debuff_entry, "bottomleft", 0, -6) - + export_buff_button:SetPoint("topleft", multiple_spells_label, "bottomleft", 0, -12) export_debuff_button:SetPoint("left",export_buff_button, "right", 2, 0) export_box:SetPoint("topleft", export_buff_button, "bottomleft", 0, -6) - + buffs_added:Refresh() debuffs_added:Refresh() - - ------------------------ ---------------------------------------------- ---------------------------------------------- ----------------------- - - f:SetScript("OnShow", function() + + newAuraPanel:SetScript("OnShow", function() buffs_added:Refresh() debuffs_added:Refresh() end) - - return f + + return newAuraPanel end -function DF:GetAllPlayerSpells (include_lower_case) +function DF:GetAllPlayerSpells(include_lower_case) local playerSpells = {} - local tab, tabTex, offset, numSpells = GetSpellTabInfo (2) + local tab, tabTex, offset, numSpells = GetSpellTabInfo(2) for i = 1, numSpells do local index = offset + i - local spellType, spellId = GetSpellBookItemInfo (index, "player") + local spellType, spellId = GetSpellBookItemInfo(index, "player") if (spellType == "SPELL") then local spellName = GetSpellInfo(spellId) tinsert(playerSpells, spellName) if (include_lower_case) then - tinsert(playerSpells, lower (spellName)) + tinsert(playerSpells, lower(spellName)) end end end return playerSpells end -function DF:SetAutoCompleteWithSpells (textentry) +function DF:SetAutoCompleteWithSpells(textentry) textentry:SetHook("OnEditFocusGained", function() - local playerSpells = DF:GetAllPlayerSpells (true) + local playerSpells = DF:GetAllPlayerSpells(true) textentry.WordList = playerSpells end) - textentry:SetAsAutoComplete ("WordList") + textentry:SetAsAutoComplete("WordList") end ---check for aura - - --- add aura - - ---handle savedvariables - - ---remove a aura - - - - - ---handle UNIT_AURA event diff --git a/libs/DF/buildmenu.lua b/libs/DF/buildmenu.lua new file mode 100644 index 00000000..6c76a4d7 --- /dev/null +++ b/libs/DF/buildmenu.lua @@ -0,0 +1,1607 @@ + +local detailsFramework = _G["DetailsFramework"] +if (not detailsFramework or not DetailsFrameworkCanLoad) then + return +end + +local unpack = unpack +local C_Timer = C_Timer +local InCombatLockdown = InCombatLockdown +local CreateFrame = CreateFrame +local PixelUtil = PixelUtil +local _ + +---@class df_menu_table : table +---@field text_template table +---@field id string an unique string or number to identify the button, from parent.widgetids[id], parent is the first argument of BuildMenu and BuildMenuVolatile +---@field namePhraseId string the phrase id (from language localization) to use on the button + +---@class df_menu_label : df_menu_table +---@field get function +---@field color table +---@field font string +---@field size number +---@field text string + +---@class df_menu_dropdown : df_menu_table +---@field type string +---@field set function +---@field get function +---@field values table +---@field name string +---@field desc string +---@field descPhraseId string +---@field hooks table + +---@class df_menu_toggle : df_menu_table +---@field set function +---@field get function +---@field name string +---@field desc string +---@field descPhraseId string +---@field hooks table +---@field width number +---@field height number +---@field boxfirst boolean + +---@class df_menu_range : df_menu_table +---@field set function +---@field get function +---@field min number +---@field max number +---@field step number +---@field name string +---@field desc string +---@field descPhraseId string +---@field hooks table +---@field thumbscale number +---@field usedecimals boolean if true allow fraction values + +---@class df_menu_color : df_menu_table +---@field set function +---@field get function +---@field name string +---@field desc string +---@field descPhraseId string +---@field hooks table +---@field boxfirst boolean + +---@class df_menu_button : df_menu_table +---@field func function the function to execute when the button is pressed +---@field param1 any the first parameter to pass to the function +---@field param2 any the second parameter to pass to the function +---@field name string text to show on the button +---@field desc string text to show on the tooltip +---@field descPhraseId string the phrase id (from language localization) to use on the tooltip +---@field hooks table a table with hooks to add to the button +---@field width number +---@field height number +---@field inline boolean +---@field icontexture any +---@field icontexcoords table + +---@class df_menu_textentry : df_menu_table +---@field func function the function to execute when enter key is pressed +---@field set function same as above 'func' +---@field get function +---@field name string text to show on the button +---@field desc string text to show on the tooltip +---@field descPhraseId string the phrase id (from language localization) to use on the tooltip +---@field hooks table a table with hooks to add to the button +---@field inline boolean if true, the widget is placed in the rigt side of the previous one +---@field align string "left", "center" or "right" +---@field nocombat boolean can't edit when in combat +---@field spacement boolean gives a little of more space from the next widget + +local onEnterHighlight = function(self) + self.highlightTexture:Show() + if (self.parent:GetScript("OnEnter")) then + self.parent:GetScript("OnEnter")(self.parent) + end +end + +local onLeaveHighlight = function(self) + self.highlightTexture:Hide() + if (self.parent:GetScript("OnLeave")) then + self.parent:GetScript("OnLeave")(self.parent) + end +end + +local createOptionHighlightTexture = function(frame, label, widgetWidth) + frame = frame.widget or frame + label = label.widget or label + + local highlightFrame = CreateFrame("frame", nil, frame) + highlightFrame:EnableMouse(true) + highlightFrame:SetFrameLevel(frame:GetFrameLevel()-1) + + PixelUtil.SetSize(highlightFrame, widgetWidth, frame:GetHeight() + 1) + PixelUtil.SetPoint(highlightFrame, "topleft", label, "topleft", -2, 5) + + highlightFrame:SetScript("OnEnter", onEnterHighlight) + highlightFrame:SetScript("OnLeave", onLeaveHighlight) + + local highlightTexture = highlightFrame:CreateTexture(nil, "overlay") + highlightTexture:SetColorTexture(1, 1, 1, 0.1) + PixelUtil.SetPoint(highlightTexture, "topleft", highlightFrame, "topleft", 0, 0) + PixelUtil.SetPoint(highlightTexture, "bottomright", highlightFrame, "bottomright", 0, 0) + highlightTexture:Hide() + + local backgroundTexture = highlightFrame:CreateTexture(nil, "artwork") + backgroundTexture:SetColorTexture(1, 1, 1) + backgroundTexture:SetVertexColor(.25, .25, .25, 0.5) + PixelUtil.SetPoint(backgroundTexture, "topleft", highlightFrame, "topleft", 0, 0) + PixelUtil.SetPoint(backgroundTexture, "bottomright", highlightFrame, "bottomright", 0, 0) + + highlightFrame.highlightTexture = highlightTexture + highlightFrame.parent = frame + + return highlightTexture +end + +local refresh_options = function(self) + for _, widget in ipairs(self.widget_list) do + if (widget._get) then + if (widget.widget_type == "label") then + if (widget._get() and not widget.languageAddonId) then + widget:SetText(widget._get()) + end + + elseif (widget.widget_type == "select") then + widget:Select(widget._get()) + + elseif (widget.widget_type == "toggle" or widget.widget_type == "range") then + widget:SetValue(widget._get()) + + elseif (widget.widget_type == "textentry") then + widget:SetText(widget._get()) + + elseif (widget.widget_type == "color") then + local default_value, g, b, a = widget._get() + if (type(default_value) == "table") then + widget:SetColor (unpack(default_value)) + + else + widget:SetColor (default_value, g, b, a) + end + end + end + end +end + +detailsFramework.internalFunctions.RefreshOptionsPanel = refresh_options + +local getFrameById = function(self, id) + return self.widgetids[id] +end + +function detailsFramework:ClearOptionsPanel(frame) + for i = 1, #frame.widget_list do + frame.widget_list[i]:Hide() + if (frame.widget_list[i].hasLabel) then + frame.widget_list[i].hasLabel:SetText("") + end + end + table.wipe(frame.widgetids) +end + +function detailsFramework:SetAsOptionsPanel(frame) + frame.RefreshOptions = refresh_options + frame.widget_list = {} + frame.widget_list_by_type = { + ["dropdown"] = {}, -- "select" + ["switch"] = {}, -- "toggle" + ["slider"] = {}, -- "range" + ["color"] = {}, -- + ["button"] = {}, -- "execute" + ["textentry"] = {}, -- + ["label"] = {}, --"text" + } + frame.widgetids = {} + frame.GetWidgetById = getFrameById +end + +local formatOptionNameWithColon = function(text, useColon) + if (text) then + if (useColon) then + text = text .. ":" + return text + else + return text + end + end +end + +local widgetsToDisableOnCombat = {} + +local getMenuWidgetVolative = function(parent, widgetType, indexTable) + local widgetObject + + if (widgetType == "label") then + widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] + if (not widgetObject) then + widgetObject = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType], "overlay") + table.insert(parent.widget_list, widgetObject) + table.insert(parent.widget_list_by_type[widgetType], widgetObject) + end + indexTable[widgetType] = indexTable[widgetType] + 1 + + elseif (widgetType == "dropdown") then + widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] + if (not widgetObject) then + widgetObject = detailsFramework:CreateDropDown(parent, function() return {} end, nil, 120, 18, nil, "$parentWidget" .. widgetType .. indexTable[widgetType]) + widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") + table.insert(parent.widget_list, widgetObject) + table.insert(parent.widget_list_by_type[widgetType], widgetObject) + + else + widgetObject:ClearHooks() + widgetObject.hasLabel.text = "" + end + indexTable[widgetType] = indexTable[widgetType] + 1 + + elseif (widgetType == "switch") then + widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] + if (not widgetObject) then + widgetObject = detailsFramework:CreateSwitch(parent, nil, true, 20, 20, nil, nil, nil, "$parentWidget" .. widgetType .. indexTable[widgetType]) + widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") + + table.insert(parent.widget_list, widgetObject) + table.insert(parent.widget_list_by_type[widgetType], widgetObject) + else + widgetObject:ClearHooks() + end + indexTable[widgetType] = indexTable[widgetType] + 1 + + elseif (widgetType == "slider") then + widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] + if (not widgetObject) then + widgetObject = detailsFramework:CreateSlider(parent, 120, 20, 1, 2, 1, 1, false, nil, "$parentWidget" .. widgetType .. indexTable[widgetType]) + widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") + + table.insert(parent.widget_list, widgetObject) + table.insert(parent.widget_list_by_type[widgetType], widgetObject) + else + widgetObject:ClearHooks() + end + indexTable[widgetType] = indexTable[widgetType] + 1 + + elseif (widgetType == "color") then + widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] + if (not widgetObject) then + widgetObject = detailsFramework:CreateColorPickButton(parent, "$parentWidget" .. widgetType .. indexTable[widgetType], nil, function()end, 1) + widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") + + table.insert(parent.widget_list, widgetObject) + table.insert(parent.widget_list_by_type[widgetType], widgetObject) + else + widgetObject:ClearHooks() + end + indexTable[widgetType] = indexTable[widgetType] + 1 + + elseif (widgetType == "button") then + widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] + if (not widgetObject) then + widgetObject = detailsFramework:CreateButton(parent, function()end, 120, 18, "", nil, nil, nil, nil, "$parentWidget" .. widgetType .. indexTable[widgetType]) + widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") + + table.insert(parent.widget_list, widgetObject) + table.insert(parent.widget_list_by_type[widgetType], widgetObject) + else + widgetObject:ClearHooks() + end + indexTable[widgetType] = indexTable[widgetType] + 1 + + elseif (widgetType == "textentry") then + widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] + if (not widgetObject) then + widgetObject = detailsFramework:CreateTextEntry(parent, function()end, 120, 18, nil, "$parentWidget" .. widgetType .. indexTable[widgetType]) + widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") + + table.insert(parent.widget_list, widgetObject) + table.insert(parent.widget_list_by_type[widgetType], widgetObject) + else + widgetObject:ClearHooks() + end + indexTable[widgetType] = indexTable[widgetType] + 1 + end + + --if the widget is inside the no combat table, remove it + for i = 1, #widgetsToDisableOnCombat do + if (widgetsToDisableOnCombat[i] == widgetObject) then + table.remove(widgetsToDisableOnCombat, i) + break + end + end + + return widgetObject +end + +--get the description phrase from the language table or use the .desc or .deschraseid +local getDescPhraseText = function(languageTable, widgetTable) + local descPhraseId = languageTable and (languageTable[widgetTable.descPhraseId] or languageTable[widgetTable.desc]) + return descPhraseId or widgetTable.descPhraseId or widgetTable.desc or widgetTable.name or "-?-" +end + +local getNamePhraseID = function(widgetTable, languageAddonId, languageTable, bIgnoreEmbed) + if (widgetTable.namePhraseId) then + return widgetTable.namePhraseId + end + + if (not languageTable) then + return + end + + local keyName = widgetTable.name + + if (widgetTable.type == "label" and widgetTable.get) then + local key = widgetTable.get() + if (key and type(key) == "string") then + keyName = key + end + end + + --embed key is when the phraseId is inside a string surounded by @ + local embedPhraseId = keyName:match("@(.-)@") + + local hasValue = detailsFramework.Language.DoesPhraseIDExistsInDefaultLanguage(languageAddonId, embedPhraseId or keyName) + if (not hasValue) then + return + end + + if (embedPhraseId and not bIgnoreEmbed) then + return embedPhraseId, true + else + return keyName + end +end + +local getNamePhraseText = function(languageTable, widgetTable, useColon, languageAddonId) + local namePhraseId, bWasEmbed = getNamePhraseID(widgetTable, languageAddonId, languageTable) + local namePhrase = languageTable and (languageTable[namePhraseId] or languageTable[widgetTable.namePhraseId] or languageTable[widgetTable.name]) + + if (bWasEmbed and widgetTable.name) then + namePhrase = widgetTable.name:gsub("@" .. namePhraseId .. "@", namePhrase) + end + + return namePhrase or formatOptionNameWithColon(widgetTable.name, useColon) or widgetTable.namePhraseId or widgetTable.name or "-?-" +end + +--volatile menu can be called several times, each time all settings are reset and a new menu is built reusing the widgets +function detailsFramework:BuildMenuVolatile(parent, menuOptions, xOffset, yOffset, height, useColon, textTemplate, dropdownTemplate, switchTemplate, switchIsCheckbox, sliderTemplate, buttonTemplate, valueChangeHook) + if (not parent.widget_list) then + detailsFramework:SetAsOptionsPanel(parent) + end + detailsFramework:ClearOptionsPanel(parent) + + local currentXOffset = xOffset or 0 + local currentYOffset = yOffset or 0 + local maxColumnWidth = 0 + + local latestInlineWidget + + local widgetIndexes = { + label = 1, + dropdown = 1, + switch = 1, + slider = 1, + color = 1, + button = 1, + textentry = 1, + } + + if (height and type(height) == "number") then + height = math.abs((height or parent:GetHeight()) - math.abs(yOffset) + 20) + height = height * -1 + else + height = parent:GetHeight() + end + + --normalize format types + for index, widgetTable in ipairs(menuOptions) do + if (widgetTable.type == "space") then + widgetTable.type = "blank" + + elseif (widgetTable.type == "fontdropdown") then + widgetTable.type = "selectfont" + elseif (widgetTable.type == "colordropdown") then + widgetTable.type = "selectcolor" + elseif (widgetTable.type == "outlinedropdown") then + widgetTable.type = "selectoutline" + elseif (widgetTable.type == "anchordropdown") then + widgetTable.type = "selectanchor" + elseif (widgetTable.type == "dropdown") then + widgetTable.type = "select" + + elseif (widgetTable.type == "switch") then + widgetTable.type = "toggle" + + elseif (widgetTable.type == "slider") then + widgetTable.type = "range" + + elseif (widgetTable.type == "button") then + widgetTable.type = "execute" + end + end + + --catch some options added in the hash part of the menu table + local bUseBoxFirstOnAllWidgets = menuOptions.always_boxfirst + local bAlignAsPairs = menuOptions.align_as_pairs + local nAlignAsPairsLength = menuOptions.align_as_pairs_string_space or 160 + local languageAddonId = menuOptions.language_addonId + local widgetWidth = menuOptions.widget_width + local widgetHeight = menuOptions.widget_height + local languageTable + + if (languageAddonId) then + languageTable = DetailsFramework.Language.GetLanguageTable(languageAddonId) + end + + for index, widgetTable in ipairs(menuOptions) do + if (not widgetTable.hidden) then + local widgetCreated + if (latestInlineWidget) then + if (not widgetTable.inline) then + latestInlineWidget = nil + currentYOffset = currentYOffset - 20 + end + end + + local extraPaddingY = 0 + + if (not widgetTable.novolatile) then + --step a line + if (widgetTable.type == "blank" or widgetTable.type == "space") then + --do nothing + + elseif (widgetTable.type == "label" or widgetTable.type == "text") then + local label = getMenuWidgetVolative(parent, "label", widgetIndexes) + widgetCreated = label + + local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable) + local namePhrase = (languageTable and (languageTable[namePhraseId] or languageTable[widgetTable.namePhraseId] or languageTable[widgetTable.name])) or (widgetTable.get and widgetTable.get()) or widgetTable.text or (widgetTable.namePhraseId) or "" + label.text = namePhrase + label.color = widgetTable.color + + if (widgetTable.font) then + label.fontface = widgetTable.font + end + + if (widgetTable.text_template or textTemplate) then + label:SetTemplate(widgetTable.text_template or textTemplate) + else + label.fontsize = widgetTable.size or 10 + end + + label._get = widgetTable.get + label.widget_type = "label" + label:ClearAllPoints() + label:SetPoint(currentXOffset, currentYOffset) + + if (widgetTable.id) then + parent.widgetids [widgetTable.id] = label + end + + --dropdowns + elseif (widgetTable.type:find("select")) then + assert(widgetTable.get, "DetailsFramework:BuildMenu(): .get() not found in the widget table for 'select'") + local dropdown = getMenuWidgetVolative(parent, "dropdown", widgetIndexes) + widgetCreated = dropdown + + if (widgetTable.type == "selectfont") then + local func = detailsFramework:CreateFontListGenerator(widgetTable.set) + dropdown:SetFunction(func) + + elseif (widgetTable.type == "selectcolor") then + local func = detailsFramework:CreateColorListGenerator(widgetTable.set) + dropdown:SetFunction(func) + + elseif (widgetTable.type == "selectanchor") then + local func = detailsFramework:CreateAnchorPointListGenerator(widgetTable.set) + dropdown:SetFunction(func) + + elseif (widgetTable.type == "selectoutline") then + local func = detailsFramework:CreateOutlineListGenerator(widgetTable.set) + dropdown:SetFunction(func) + else + dropdown:SetFunction(widgetTable.values) + end + + dropdown:Refresh() + dropdown:Select(widgetTable.get()) + dropdown:SetTemplate(dropdownTemplate) + + if (widgetWidth) then + dropdown:SetWidth(widgetWidth) + end + if (widgetHeight) then + dropdown:SetHeight(widgetHeight) + end + + local descPhrase = getDescPhraseText(languageTable, widgetTable) + dropdown:SetTooltip(descPhrase) + dropdown._get = widgetTable.get + dropdown.widget_type = "select" + + local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon, languageAddonId) + dropdown.hasLabel.text = namePhrase + + dropdown.hasLabel:SetTemplate(widgetTable.text_template or textTemplate) + + --as these are reused widgets, clean the previous point + dropdown:ClearAllPoints() + dropdown.hasLabel:ClearAllPoints() + + if (bAlignAsPairs) then + dropdown.hasLabel:SetPoint(currentXOffset, currentYOffset) + dropdown:SetPoint("left", dropdown.hasLabel, "left", nAlignAsPairsLength, 0) + else + dropdown:SetPoint("left", dropdown.hasLabel, "right", 2, 0) + dropdown.hasLabel:SetPoint(currentXOffset, currentYOffset) + end + + --global callback + if (valueChangeHook) then + dropdown:SetHook("OnOptionSelected", valueChangeHook) + end + + --hook list (hook list is wiped when getting the widget) + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + dropdown:SetHook(hookName, hookFunc) + end + end + + if (widgetTable.id) then + parent.widgetids[widgetTable.id] = dropdown + end + + local widgetTotalSize = dropdown.hasLabel.widget:GetStringWidth() + 140 + 4 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + --switchs + elseif (widgetTable.type == "toggle" or widgetTable.type == "switch") then + local switch = getMenuWidgetVolative(parent, "switch", widgetIndexes) + widgetCreated = switch + + switch:SetValue(widgetTable.get()) + switch:SetTemplate(switchTemplate) + switch:SetAsCheckBox() --it's always a checkbox on volatile menu + + local descPhrase = getDescPhraseText(languageTable, widgetTable) + switch:SetTooltip(descPhrase) + switch._get = widgetTable.get + switch.widget_type = "toggle" + switch.OnSwitch = widgetTable.set + + if (valueChangeHook) then + switch:SetHook("OnSwitch", valueChangeHook) + end + + --hook list + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + switch:SetHook(hookName, hookFunc) + end + end + + if (widgetTable.width) then + switch:SetWidth(widgetTable.width) + end + if (widgetTable.height) then + switch:SetHeight(widgetTable.height) + end + + local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon, languageAddonId) + switch.hasLabel.text = namePhrase + switch.hasLabel:SetTemplate(widgetTable.text_template or textTemplate) + + switch:ClearAllPoints() + switch.hasLabel:ClearAllPoints() + + if (bAlignAsPairs) then + switch.hasLabel:SetPoint(currentXOffset, currentYOffset) + switch:SetPoint("left", switch.hasLabel, "left", nAlignAsPairsLength, 0) + else + if (widgetTable.boxfirst or bUseBoxFirstOnAllWidgets) then + switch:SetPoint(currentXOffset, currentYOffset) + switch.hasLabel:SetPoint("left", switch, "right", 2) + + local nextWidgetTable = menuOptions[index+1] + if (nextWidgetTable) then + if (nextWidgetTable.type ~= "blank" and nextWidgetTable.type ~= "breakline" and nextWidgetTable.type ~= "toggle" and nextWidgetTable.type ~= "color") then + extraPaddingY = 4 + end + end + else + switch.hasLabel:SetPoint(currentXOffset, currentYOffset) + switch:SetPoint("left", switch.hasLabel, "right", 2) + end + end + + if (widgetTable.id) then + parent.widgetids [widgetTable.id] = switch + end + + local widgetTotalSize = switch.hasLabel:GetStringWidth() + 32 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + --slider + elseif (widgetTable.type == "range" or widgetTable.type == "slider") then + local slider = getMenuWidgetVolative(parent, "slider", widgetIndexes) + widgetCreated = slider + + if (widgetTable.usedecimals) then + slider.slider:SetValueStep(0.01) + else + slider.slider:SetValueStep(widgetTable.step or 1) + end + slider.useDecimals = widgetTable.usedecimals + + slider.slider:SetMinMaxValues(widgetTable.min, widgetTable.max) + slider.slider:SetValue(widgetTable.get()) + slider.ivalue = slider.slider:GetValue() + + slider:SetTemplate(sliderTemplate) + + if (widgetWidth) then + slider:SetWidth(widgetWidth) + end + if (widgetHeight) then + slider:SetHeight(widgetHeight) + end + + local descPhrase = getDescPhraseText(languageTable, widgetTable) + slider:SetTooltip(descPhrase) + slider._get = widgetTable.get + slider.widget_type = "range" + slider:SetHook("OnValueChange", widgetTable.set) + + if (valueChangeHook) then + slider:SetHook("OnValueChange", valueChangeHook) + end + + if (widgetTable.thumbscale) then + slider:SetThumbSize (slider.thumb.originalWidth * widgetTable.thumbscale, nil) + else + slider:SetThumbSize (slider.thumb.originalWidth * 1.3, nil) + end + + --hook list + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + slider:SetHook(hookName, hookFunc) + end + end + + local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon, languageAddonId) + slider.hasLabel.text = namePhrase + slider.hasLabel:SetTemplate(widgetTable.text_template or textTemplate) + + slider:ClearAllPoints() + slider.hasLabel:ClearAllPoints() + + if (bAlignAsPairs) then + slider.hasLabel:SetPoint(currentXOffset, currentYOffset) + slider:SetPoint("left", slider.hasLabel, "left", nAlignAsPairsLength, 0) + else + slider:SetPoint("left", slider.hasLabel, "right", 2) + slider.hasLabel:SetPoint(currentXOffset, currentYOffset) + end + + if (widgetTable.id) then + parent.widgetids[widgetTable.id] = slider + end + + local widgetTotalSize = slider.hasLabel:GetStringWidth() + 146 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + --color + elseif (widgetTable.type == "color" or widgetTable.type == "color") then + local colorpick = getMenuWidgetVolative(parent, "color", widgetIndexes) + widgetCreated = colorpick + + colorpick.color_callback = widgetTable.set --callback + colorpick:SetTemplate(buttonTemplate) + colorpick:SetSize(18, 18) + + local descPhrase = getDescPhraseText(languageTable, widgetTable) + colorpick:SetTooltip(descPhrase) + colorpick._get = widgetTable.get + colorpick.widget_type = "color" + + local default_value, g, b, a = widgetTable.get() + if (type(default_value) == "table") then + colorpick:SetColor(unpack(default_value)) + else + colorpick:SetColor(default_value, g, b, a) + end + + if (valueChangeHook) then + colorpick:SetHook("OnColorChanged", valueChangeHook) + end + + --hook list + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + colorpick:SetHook(hookName, hookFunc) + end + end + + local label = colorpick.hasLabel + + local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon, languageAddonId) + label.text = namePhrase + label:SetTemplate(widgetTable.text_template or textTemplate) + + label:ClearAllPoints() + colorpick:ClearAllPoints() + + if (bAlignAsPairs) then + label:SetPoint(currentXOffset, currentYOffset) + colorpick:SetPoint("left", label, "left", nAlignAsPairsLength, 0) + else + if (widgetTable.boxfirst or bUseBoxFirstOnAllWidgets) then + label:SetPoint("left", colorpick, "right", 2, 0) + colorpick:SetPoint(currentXOffset, currentYOffset) + extraPaddingY = 1 + else + colorpick:SetPoint("left", label, "right", 2, 0) + label:SetPoint(currentXOffset, currentYOffset) + end + end + + if (widgetTable.id) then + parent.widgetids[widgetTable.id] = colorpick + end + + local widgetTotalSize = label:GetStringWidth() + 32 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + --button + elseif (widgetTable.type == "execute" or widgetTable.type == "button") then + local button = getMenuWidgetVolative(parent, "button", widgetIndexes) + widgetCreated = button + + button:SetTemplate(buttonTemplate) + button:SetSize(widgetWidth or widgetTable.width or 120, widgetHeight or widgetTable.height or 18) + button:SetClickFunction(widgetTable.func, widgetTable.param1, widgetTable.param2) + + local textTemplate = widgetTable.text_template or textTemplate or detailsFramework.font_templates["ORANGE_FONT_TEMPLATE"] + button.textcolor = textTemplate.color + button.textfont = textTemplate.font + button.textsize = textTemplate.size + + local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon, languageAddonId) + button.text = namePhrase + + button:ClearAllPoints() + + if (bAlignAsPairs) then + button:SetPoint(currentXOffset, currentYOffset) + else + if (widgetTable.inline) then + if (latestInlineWidget) then + button:SetPoint("left", latestInlineWidget, "right", 2, 0) + latestInlineWidget = button + else + button:SetPoint(currentXOffset, currentYOffset) + latestInlineWidget = button + end + else + button:SetPoint(currentXOffset, currentYOffset) + end + end + + local descPhrase = getDescPhraseText(languageTable, widgetTable) + button:SetTooltip(descPhrase) + button.widget_type = "execute" + + --hook list + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + button:SetHook(hookName, hookFunc) + end + end + + if (widgetTable.width) then + button:SetWidth(widgetTable.width) + end + if (widgetTable.height) then + button:SetHeight(widgetTable.height) + end + + if (widgetTable.id) then + parent.widgetids[widgetTable.id] = button + end + + local widgetTotalSize = button:GetWidth() + 4 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + --textentry + elseif (widgetTable.type == "textentry") then + local textentry = getMenuWidgetVolative(parent, "textentry", widgetIndexes) + widgetCreated = textentry + + textentry:SetCommitFunction(widgetTable.func or widgetTable.set) + textentry:SetTemplate(widgetTable.template or widgetTable.button_template or buttonTemplate) + textentry:SetSize(widgetWidth or widgetTable.width or 120, widgetHeight or widgetTable.height or 18) + + local descPhrase = getDescPhraseText(languageTable, widgetTable) + textentry:SetTooltip(descPhrase) + textentry.text = widgetTable.get() + textentry._get = widgetTable.get + textentry.widget_type = "textentry" + + textentry:SetHook("OnEnterPressed", function(...) + local upFunc = widgetTable.func or widgetTable.set + upFunc(...) + if (valueChangeHook) then + valueChangeHook() + end + end) + textentry:SetHook("OnEditFocusLost", function(...) + local upFunc = widgetTable.func or widgetTable.set + upFunc(...) + if (valueChangeHook) then + valueChangeHook() + end + end) + + local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon, languageAddonId) + textentry.hasLabel.text = namePhrase + textentry.hasLabel:SetTemplate(widgetTable.text_template or textTemplate) + + textentry.hasLabel:ClearAllPoints() + textentry:ClearAllPoints() + + if (bAlignAsPairs) then + textentry.hasLabel:SetPoint(currentXOffset, currentYOffset) + textentry:SetPoint("left", textentry.hasLabel, "left", nAlignAsPairsLength, 0) + else + textentry:SetPoint("left", textentry.hasLabel, "right", 2) + textentry.hasLabel:SetPoint(currentXOffset, currentYOffset) + end + + --hook list + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + textentry:SetHook(hookName, hookFunc) + end + end + + if (widgetTable.id) then + parent.widgetids[widgetTable.id] = textentry + end + + local widgetTotalSize = textentry.hasLabel:GetStringWidth() + 64 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + end --end loop + + if (widgetTable.nocombat) then + table.insert(widgetsToDisableOnCombat, widgetCreated) + end + + if (not widgetTable.inline) then + if (widgetTable.spacement) then + currentYOffset = currentYOffset - 30 + else + currentYOffset = currentYOffset - 20 + end + end + + if (extraPaddingY > 0) then + currentYOffset = currentYOffset - extraPaddingY + end + + if (widgetTable.type == "breakline" or currentYOffset < height) then + currentYOffset = yOffset + currentXOffset = currentXOffset + maxColumnWidth + 20 + maxColumnWidth = 0 + end + + if widgetCreated then + widgetCreated:Show() + end + end + end + end + + detailsFramework.RefreshUnsafeOptionsWidgets() +end + +local getDescripttionPhraseID = function(widgetTable, languageAddonId, languageTable) + if (widgetTable.descPhraseId) then + return widgetTable.descPhraseId + end + + if (not languageTable) then + return + end + + local hasValue = detailsFramework.Language.DoesPhraseIDExistsInDefaultLanguage(languageAddonId, widgetTable.desc) + if (not hasValue) then + return + end + + return widgetTable.desc +end + + ---classes used by the menu builder on the menuOptions table on both functions BuildMenu and BuildMenuVolatile + ---the menuOptions consists of a table with several tables inside in array, each table is a widget to be created + ---class df_menu_label is used when the sub table of menuOptions has a key named "type" with the value "label" or "text" + function detailsFramework:BuildMenu(parent, menuOptions, xOffset, yOffset, height, useColon, textTemplate, dropdownTemplate, switchTemplate, switchIsCheckbox, sliderTemplate, buttonTemplate, valueChangeHook) + --how many widgets has been created on this line loop pass + local amountLineWidgetCreated = 0 + local latestInlineWidget + + --normalize format types + for index, widgetTable in ipairs(menuOptions) do + if (widgetTable.type == "space") then + widgetTable.type = "blank" + + elseif (widgetTable.type == "fontdropdown") then + widgetTable.type = "selectfont" + elseif (widgetTable.type == "colordropdown") then + widgetTable.type = "selectcolor" + elseif (widgetTable.type == "outlinedropdown") then + widgetTable.type = "selectoutline" + elseif (widgetTable.type == "anchordropdown") then + widgetTable.type = "selectanchor" + elseif (widgetTable.type == "dropdown") then + widgetTable.type = "select" + + elseif (widgetTable.type == "switch") then + widgetTable.type = "toggle" + + elseif (widgetTable.type == "slider") then + widgetTable.type = "range" + + elseif (widgetTable.type == "button") then + widgetTable.type = "execute" + end + end + + --catch some options added in the hash part of the menu table + local bUseBoxFirstOnAllWidgets = menuOptions.always_boxfirst + local widgetWidth = menuOptions.widget_width --a width to be used on all widgets + local widgetHeight = menuOptions.widget_height --a height to be used on all widgets + local bAlignAsPairs = menuOptions.align_as_pairs + local nAlignAsPairsLength = menuOptions.align_as_pairs_string_space or 160 + local nAlignAsPairsSpacing = menuOptions.align_as_pairs_spacing or 20 + + --if a scrollbox is passed, the height can be ignored + --the scrollBox child will be used as the parent, and the height of the child will be resized to fit the widgets + local bUseScrollFrame = menuOptions.use_scrollframe + local biggestColumnHeight = 0 --used to resize the scrollbox child when a scrollbox is passed + + if (not bUseScrollFrame) then + if (height and type(height) == "number") then + height = math.abs((height or parent:GetHeight()) - math.abs(yOffset) + 20) + height = height * -1 + else + height = parent:GetHeight() + end + else + local width, height = parent:GetSize() + parent = parent:GetScrollChild() + parent:SetSize(width, height) + end + + local languageAddonId = menuOptions.language_addonId + local languageTable + + if (languageAddonId) then + languageTable = DetailsFramework.Language.GetLanguageTable(languageAddonId) + end + + if (not parent.widget_list) then + detailsFramework:SetAsOptionsPanel(parent) + end + + local currentXOffset = xOffset or 0 + local currentYOffset = yOffset or 0 + local maxColumnWidth = 0 --biggest width of widget + text size on the current column loop pass + local maxWidgetWidth = 0 --biggest widget width on the current column loop pass + local maxWidth = parent:GetWidth() --total width the buildmenu can use - not in use + + for index, widgetTable in ipairs(menuOptions) do + if (not widgetTable.hidden) then + local widgetCreated + if (latestInlineWidget) then + if (not widgetTable.inline) then + latestInlineWidget = nil + currentYOffset = currentYOffset - 28 + end + end + + local extraPaddingY = 0 + + if (widgetTable.type == "blank") then + --do nothing + + elseif (widgetTable.type == "label" or widgetTable.type == "text") then + ---@cast widgetTable df_menu_label + + local label = detailsFramework:CreateLabel(parent, "", widgetTable.text_template or textTemplate or widgetTable.size, widgetTable.color, widgetTable.font, nil, "$parentWidget" .. index, "overlay") + label._get = widgetTable.get + label.widget_type = "label" + label:SetPoint(currentXOffset, currentYOffset) + + local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable) + if (namePhraseId) then + DetailsFramework.Language.RegisterObject(languageAddonId, label.widget, namePhraseId) + label.languageAddonId = languageAddonId + else + local textToSet = (widgetTable.get and widgetTable.get()) or widgetTable.text or "" + label:SetText(textToSet) + end + + --store the widget created into the overall table and the widget by type + table.insert(parent.widget_list, label) + table.insert(parent.widget_list_by_type.label, label) + + amountLineWidgetCreated = amountLineWidgetCreated + 1 + + if (widgetTable.id) then + parent.widgetids[widgetTable.id] = label + end + + elseif (widgetTable.type:find("select")) then + ---@cast widgetTable df_menu_dropdown + + assert(widgetTable.get, "DetailsFramework:BuildMenu(): .get not found in the widget table for 'select'") + + local defaultHeight = 18 + + local dropdown + if (widgetTable.type == "selectfont") then + dropdown = detailsFramework:CreateFontDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate) + + elseif (widgetTable.type == "selectcolor") then + dropdown = detailsFramework:CreateColorDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate) + + elseif (widgetTable.type == "selectanchor") then + dropdown = detailsFramework:CreateAnchorPointDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate) + + elseif (widgetTable.type == "selectoutline") then + dropdown = detailsFramework:CreateOutlineDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate) + else + dropdown = detailsFramework:NewDropDown(parent, nil, "$parentWidget" .. index, nil, widgetWidth or 140, widgetHeight or defaultHeight, widgetTable.values, widgetTable.get(), dropdownTemplate) + end + + local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) + DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, dropdown, "have_tooltip", descPhraseId, widgetTable.desc) + + dropdown._get = widgetTable.get + dropdown.widget_type = "select" + + local label = detailsFramework:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12) + local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true) + DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon)) + + dropdown.addonId = languageAddonId + if (languageAddonId) then + detailsFramework.Language.RegisterCallback(languageAddonId, function(addonId, languageId, ...) dropdown:Select(dropdown:GetValue()) end) + C_Timer.After(0.1, function() dropdown:Select(dropdown:GetValue()) end) + end + + if (bAlignAsPairs) then + PixelUtil.SetPoint(label.widget, "topleft", dropdown:GetParent(), "topleft", currentXOffset, currentYOffset) + PixelUtil.SetPoint(dropdown.widget, "left", label.widget, "left", nAlignAsPairsLength, 0) + createOptionHighlightTexture(dropdown, label, (widgetWidth or 140) + nAlignAsPairsLength + 5) + else + dropdown:SetPoint("left", label, "right", 2, 0) + label:SetPoint(currentXOffset, currentYOffset) + end + + dropdown.hasLabel = label + + --global callback + if (valueChangeHook) then + dropdown:SetHook("OnOptionSelected", valueChangeHook) + end + + --hook list + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + dropdown:SetHook(hookName, hookFunc) + end + end + + if (widgetTable.id) then + parent.widgetids[widgetTable.id] = dropdown + end + + local widgetTotalSize = label.widget:GetStringWidth() + 144 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + if (dropdown:GetWidth() > maxWidgetWidth) then + maxWidgetWidth = dropdown:GetWidth() + end + + --store the widget created into the overall table and the widget by type + table.insert(parent.widget_list, dropdown) + table.insert(parent.widget_list_by_type.dropdown, dropdown) + + widgetCreated = dropdown + amountLineWidgetCreated = amountLineWidgetCreated + 1 + + elseif (widgetTable.type == "toggle") then + ---@cast widgetTable df_menu_toggle + + local switch = detailsFramework:NewSwitch(parent, nil, "$parentWidget" .. index, nil, 60, 20, nil, nil, widgetTable.get(), nil, nil, nil, nil, switchTemplate) + + local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) + DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, switch, "have_tooltip", descPhraseId, widgetTable.desc) + + switch._get = widgetTable.get + switch.widget_type = "toggle" + switch.OnSwitch = widgetTable.set + + if (switchIsCheckbox) then + switch:SetAsCheckBox() + end + + if (valueChangeHook) then + switch:SetHook("OnSwitch", valueChangeHook) + end + + --hook list + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + switch:SetHook(hookName, hookFunc) + end + end + + if (widgetTable.width) then + PixelUtil.SetWidth(switch.widget, widgetTable.width) + end + if (widgetTable.height) then + PixelUtil.SetHeight(switch.widget, widgetTable.height) + end + + local label = detailsFramework:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12) + + local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true) + DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon)) + + if (bAlignAsPairs) then + PixelUtil.SetPoint(label.widget, "topleft", switch:GetParent(), "topleft", currentXOffset, currentYOffset) + PixelUtil.SetPoint(switch.widget, "left", label.widget, "left", nAlignAsPairsLength, 0) + createOptionHighlightTexture(switch, label, (widgetWidth or 140) + nAlignAsPairsLength + 5) + else + if (widgetTable.boxfirst or bUseBoxFirstOnAllWidgets) then + switch:SetPoint(currentXOffset, currentYOffset) + label:SetPoint("left", switch, "right", 2) + + local nextWidgetTable = menuOptions[index+1] + if (nextWidgetTable) then + if (nextWidgetTable.type ~= "blank" and nextWidgetTable.type ~= "breakline" and nextWidgetTable.type ~= "toggle" and nextWidgetTable.type ~= "color") then + extraPaddingY = 4 + end + end + else + label:SetPoint(currentXOffset, currentYOffset) + switch:SetPoint("left", label, "right", 2, 0) + end + end + switch.hasLabel = label + + if (widgetTable.id) then + parent.widgetids[widgetTable.id] = switch + end + + local widgetTotalSize = label.widget:GetStringWidth() + 32 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + if (switch:GetWidth() > maxWidgetWidth) then + maxWidgetWidth = switch:GetWidth() + end + + --store the widget created into the overall table and the widget by type + table.insert(parent.widget_list, switch) + table.insert(parent.widget_list_by_type.switch, switch) + + widgetCreated = switch + amountLineWidgetCreated = amountLineWidgetCreated + 1 + + elseif (widgetTable.type == "range") then + ---@cast widgetTable df_menu_range + + assert(widgetTable.get, "DetailsFramework:BuildMenu(): .get not found in the widget table for 'range'") + local bIsDecimals = widgetTable.usedecimals + local slider = detailsFramework:NewSlider(parent, nil, "$parentWidget" .. index, nil, widgetWidth or 140, widgetHeight or 18, widgetTable.min, widgetTable.max, widgetTable.step, widgetTable.get(), bIsDecimals, nil, nil, sliderTemplate) + + local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) + DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, slider, "have_tooltip", descPhraseId, widgetTable.desc) + + slider._get = widgetTable.get + slider.widget_type = "range" + slider:SetHook("OnValueChange", widgetTable.set) + + if (widgetTable.thumbscale) then + slider:SetThumbSize(slider.thumb:GetWidth() * widgetTable.thumbscale, nil) + else + slider:SetThumbSize(slider.thumb:GetWidth() * 1.3, nil) + end + + if (valueChangeHook) then + slider:SetHook("OnValueChange", valueChangeHook) + end + + --hook list + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + slider:SetHook(hookName, hookFunc) + end + end + + local label = detailsFramework:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12) + local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true) + DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon)) + + if (bAlignAsPairs) then + PixelUtil.SetPoint(label.widget, "topleft", slider:GetParent(), "topleft", currentXOffset, currentYOffset) + PixelUtil.SetPoint(slider.widget, "left", label.widget, "left", nAlignAsPairsLength, 0) + createOptionHighlightTexture(slider, label, (widgetWidth or 140) + nAlignAsPairsLength + 5) + else + slider:SetPoint("left", label, "right", 2) + label:SetPoint(currentXOffset, currentYOffset) + end + slider.hasLabel = label + + if (widgetTable.id) then + parent.widgetids[widgetTable.id] = slider + end + + local widgetTotalSize = label.widget:GetStringWidth() + 146 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + if (slider:GetWidth() > maxWidgetWidth) then + maxWidgetWidth = slider:GetWidth() + end + + --store the widget created into the overall table and the widget by type + table.insert(parent.widget_list, slider) + table.insert(parent.widget_list_by_type.slider, slider) + + widgetCreated = slider + amountLineWidgetCreated = amountLineWidgetCreated + 1 + + elseif (widgetTable.type == "color") then + ---@cast widgetTable df_menu_color + assert(widgetTable.get, "DetailsFramework:BuildMenu(): .get not found in the widget table for 'color'") + local colorpick = detailsFramework:NewColorPickButton(parent, "$parentWidget" .. index, nil, widgetTable.set, nil, buttonTemplate) + + local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) + DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, colorpick, "have_tooltip", descPhraseId, widgetTable.desc) + + colorpick._get = widgetTable.get + colorpick.widget_type = "color" + colorpick:SetSize(18, 18) + + local r, g, b, a = detailsFramework:ParseColors(widgetTable.get()) + colorpick:SetColor(r, g, b, a) + + if (valueChangeHook) then + colorpick:SetHook("OnColorChanged", valueChangeHook) + end + + --hook list + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + colorpick:SetHook(hookName, hookFunc) + end + end + + local label = detailsFramework:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12) + local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true) + DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon)) + + if (bAlignAsPairs) then + label:SetPoint(currentXOffset, currentYOffset) + colorpick:SetPoint("left", label, "left", nAlignAsPairsLength, 0) + createOptionHighlightTexture(colorpick, label, (widgetWidth or 140) + nAlignAsPairsLength + 5) + else + if (widgetTable.boxfirst or bUseBoxFirstOnAllWidgets) then + label:SetPoint("left", colorpick, "right", 2) + colorpick:SetPoint(currentXOffset, currentYOffset) + extraPaddingY = 1 + else + colorpick:SetPoint("left", label, "right", 2) + label:SetPoint(currentXOffset, currentYOffset) + end + end + + colorpick.hasLabel = label + + if (widgetTable.id) then + parent.widgetids[widgetTable.id] = colorpick + end + + local widgetTotalSize = label.widget:GetStringWidth() + 32 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + if (colorpick:GetWidth() > maxWidgetWidth) then + maxWidgetWidth = colorpick:GetWidth() + end + + --store the widget created into the overall table and the widget by type + table.insert(parent.widget_list, colorpick) + table.insert(parent.widget_list_by_type.color, colorpick) + + widgetCreated = colorpick + amountLineWidgetCreated = amountLineWidgetCreated + 1 + + elseif (widgetTable.type == "execute") then + ---@cast widgetTable df_menu_button + + local button = detailsFramework:NewButton(parent, nil, "$parentWidget" .. index, nil, widgetWidth or 120, widgetHeight or 18, widgetTable.func, widgetTable.param1, widgetTable.param2, nil, "", nil, buttonTemplate, textTemplate) + + local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true) + DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, button.widget, namePhraseId, widgetTable.name) + + if (not buttonTemplate) then + button:InstallCustomTexture() + end + + if (widgetTable.inline) then + if (latestInlineWidget) then + button:SetPoint("left", latestInlineWidget, "right", 2, 0) + latestInlineWidget = button + else + button:SetPoint(currentXOffset, currentYOffset) + latestInlineWidget = button + end + else + button:SetPoint(currentXOffset, currentYOffset) + end + + local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) + DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, button, "have_tooltip", descPhraseId, widgetTable.desc) + + button.widget_type = "execute" + + --button icon + if (widgetTable.icontexture) then + button:SetIcon(widgetTable.icontexture, nil, nil, nil, widgetTable.icontexcoords, nil, nil, 2) + end + + --hook list + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + button:SetHook(hookName, hookFunc) + end + end + + if (widgetTable.id) then + parent.widgetids [widgetTable.id] = button + end + + if (widgetTable.width and not widgetWidth) then + button:SetWidth(widgetTable.width) + end + if (widgetTable.height and not widgetHeight) then + button:SetHeight(widgetTable.height) + end + + local widgetTotalSize = button:GetWidth() + 4 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + if (button:GetWidth() > maxWidgetWidth) then + maxWidgetWidth = button:GetWidth() + end + + --store the widget created into the overall table and the widget by type + table.insert(parent.widget_list, button) + table.insert(parent.widget_list_by_type.button, button) + + widgetCreated = button + amountLineWidgetCreated = amountLineWidgetCreated + 1 + + elseif (widgetTable.type == "textentry") then + ---@cast widgetTable df_menu_textentry + + local textentry = detailsFramework:CreateTextEntry(parent, widgetTable.func or widgetTable.set, widgetWidth or 120, widgetHeight or 18, nil, "$parentWidget" .. index, nil, buttonTemplate) + textentry.align = widgetTable.align or "left" + + local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) + DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, textentry, "have_tooltip", descPhraseId, widgetTable.desc) + + textentry.text = widgetTable.get() + textentry._get = widgetTable.get + textentry.widget_type = "textentry" + textentry:SetHook("OnEnterPressed", widgetTable.func or widgetTable.set) + textentry:SetHook("OnEditFocusLost", widgetTable.func or widgetTable.set) + + local label = detailsFramework:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12) + + local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true) + DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon)) + + if (bAlignAsPairs) then + label:SetPoint(currentXOffset, currentYOffset) + textentry:SetPoint("left", label, "left", nAlignAsPairsLength, 0) + createOptionHighlightTexture(textentry, label, (widgetWidth or 140) + nAlignAsPairsLength + 5) + else + textentry:SetPoint("left", label, "right", 2) + label:SetPoint(currentXOffset, currentYOffset) + end + + textentry.hasLabel = label + + --hook list + if (widgetTable.hooks) then + for hookName, hookFunc in pairs(widgetTable.hooks) do + textentry:SetHook(hookName, hookFunc) + end + end + + if (widgetTable.id) then + parent.widgetids [widgetTable.id] = textentry + end + + local widgetTotalSize = label.widget:GetStringWidth() + 64 + if (widgetTotalSize > maxColumnWidth) then + maxColumnWidth = widgetTotalSize + end + + if (textentry:GetWidth() > maxWidgetWidth) then + maxWidgetWidth = textentry:GetWidth() + end + + --store the widget created into the overall table and the widget by type + table.insert(parent.widget_list, textentry) + table.insert(parent.widget_list_by_type.textentry, textentry) + + widgetCreated = textentry + amountLineWidgetCreated = amountLineWidgetCreated + 1 + end + + if (widgetTable.nocombat) then + table.insert(widgetsToDisableOnCombat, widgetCreated) + end + + if (not widgetTable.inline) then + if (widgetTable.spacement) then + currentYOffset = currentYOffset - 30 + else + currentYOffset = currentYOffset - 20 + end + end + + if (extraPaddingY > 0) then + currentYOffset = currentYOffset - extraPaddingY + end + + if (bUseScrollFrame) then + if (widgetTable.type == "breakline") then + biggestColumnHeight = math.min(currentYOffset, biggestColumnHeight) + currentYOffset = yOffset + + if (bAlignAsPairs) then + currentXOffset = currentXOffset + nAlignAsPairsLength + (widgetWidth or maxWidgetWidth) + nAlignAsPairsSpacing + else + currentXOffset = currentXOffset + maxColumnWidth + 20 + end + + amountLineWidgetCreated = 0 + maxColumnWidth = 0 + maxWidgetWidth = 0 + end + else + if (widgetTable.type == "breakline" or currentYOffset < height) then + currentYOffset = yOffset + currentXOffset = currentXOffset + maxColumnWidth + 20 + amountLineWidgetCreated = 0 + maxColumnWidth = 0 + end + end + end + end + + if (bUseScrollFrame) then + parent:SetHeight(biggestColumnHeight * -1) + end + + detailsFramework.RefreshUnsafeOptionsWidgets() + end + + + local lockNotSafeWidgetsForCombat = function() + for _, widget in ipairs(widgetsToDisableOnCombat) do + widget:Disable() + end + end + + local unlockNotSafeWidgetsForCombat = function() + for _, widget in ipairs(widgetsToDisableOnCombat) do + widget:Enable() + end + end + + function detailsFramework.RefreshUnsafeOptionsWidgets() + if (detailsFramework.PlayerHasCombatFlag) then + lockNotSafeWidgetsForCombat() + else + unlockNotSafeWidgetsForCombat() + end + end + + detailsFramework.PlayerHasCombatFlag = false + local ProtectCombatFrame = CreateFrame("frame") + ProtectCombatFrame:RegisterEvent("PLAYER_REGEN_ENABLED") + ProtectCombatFrame:RegisterEvent("PLAYER_REGEN_DISABLED") + ProtectCombatFrame:RegisterEvent("PLAYER_ENTERING_WORLD") + ProtectCombatFrame:SetScript("OnEvent", function(self, event) + if (event == "PLAYER_ENTERING_WORLD") then + if (InCombatLockdown()) then + detailsFramework.PlayerHasCombatFlag = true + else + detailsFramework.PlayerHasCombatFlag = false + end + detailsFramework.RefreshUnsafeOptionsWidgets() + + elseif (event == "PLAYER_REGEN_ENABLED") then + detailsFramework.PlayerHasCombatFlag = false + detailsFramework.RefreshUnsafeOptionsWidgets() + + elseif (event == "PLAYER_REGEN_DISABLED") then + detailsFramework.PlayerHasCombatFlag = true + detailsFramework.RefreshUnsafeOptionsWidgets() + end + end) + + function detailsFramework:CreateInCombatTexture(frame) + if (detailsFramework.debug and not frame) then + error("Details! Framework: CreateInCombatTexture invalid frame on parameter 1.") + end + + local inCombatBackgroundTexture = detailsFramework:CreateImage(frame) + inCombatBackgroundTexture:SetColorTexture(.6, 0, 0, .1) + inCombatBackgroundTexture:Hide() + + local inCombatLabel = detailsFramework:CreateLabel(frame, "you are in combat", 24, "silver") + inCombatLabel:SetPoint("right", inCombatBackgroundTexture, "right", -10, 0) + inCombatLabel:Hide() + + frame:RegisterEvent("PLAYER_REGEN_DISABLED") + frame:RegisterEvent("PLAYER_REGEN_ENABLED") + + frame:SetScript("OnEvent", function(self, event) + if (event == "PLAYER_REGEN_DISABLED") then + inCombatBackgroundTexture:Show() + inCombatLabel:Show() + + elseif (event == "PLAYER_REGEN_ENABLED") then + inCombatBackgroundTexture:Hide() + inCombatLabel:Hide() + end + end) + + return inCombatBackgroundTexture + end \ No newline at end of file diff --git a/libs/DF/button.lua b/libs/DF/button.lua index ec77f7c5..b292b106 100644 --- a/libs/DF/button.lua +++ b/libs/DF/button.lua @@ -275,6 +275,7 @@ detailsFramework:Mixin(ButtonMetaFunctions, detailsFramework.ScriptHookMixin) --methods ---change the function which will be called when the button is pressed + ---callback function will receive the blizzard button as first parameter, click type as second, param1 and param2 as third and fourth ---@param func function ---@param param1 any ---@param param2 any @@ -381,6 +382,7 @@ detailsFramework:Mixin(ButtonMetaFunctions, detailsFramework.ScriptHookMixin) end ---add an icon to the left of the button text + ---short method truncates the text: false = do nothing, nil = increate the button width, 1 = decrease the font size, 2 = truncate the text ---@param texture any ---@param width number|nil ---@param height number|nil @@ -463,6 +465,9 @@ detailsFramework:Mixin(ButtonMetaFunctions, detailsFramework.ScriptHookMixin) textSize = textSize - 1 end end + + elseif (shortMethod == 2) then + detailsFramework:TruncateText(self.button.text, self:GetWidth() - self.icon:GetWidth() - 15) end end end @@ -740,11 +745,11 @@ function ButtonMetaFunctions:SetTemplate(template) end if (template.width) then - self:SetWidth(template.width) + PixelUtil.SetWidth(self.button, template.width) end if (template.height) then - self:SetHeight(template.height) + PixelUtil.SetHeight(self.button, template.height) end if (template.backdrop) then @@ -809,8 +814,8 @@ end --object constructor local onDisableFunc = function(self) self.texture_disabled:Show() - self.texture_disabled:SetVertexColor(0, 0, 0) - self.texture_disabled:SetAlpha(.5) + self.texture_disabled:SetVertexColor(0.1, 0.1, 0.1) + self.texture_disabled:SetAlpha(.834) end local onEnableFunc = function(self) @@ -835,7 +840,7 @@ end self:SetScript("OnEnable", onEnableFunc) end - ---@class df_button : button + ---@class df_button : button, df_scripthookmixin ---@field widget button ---@field tooltip string ---@field shown boolean @@ -851,13 +856,14 @@ end ---@field textcolor any ---@field textfont string ---@field textsize number + ---@field icon texture created after calling SetIcon() ---@field SetTemplate fun(self: df_button, template: table) set the button visual by a template ---@field RightClick fun(self: df_button) right click the button executing its right click function ---@field Exec fun(self: df_button) execute the button function for the left button ---@field Disable fun(self: df_button) disable the button ---@field Enable fun(self: df_button) enable the button ---@field IsEnabled fun(self: df_button) : boolean returns true if the button is enabled - ---@field SetIcon fun(self: df_button,texture: string, width: number|nil, height: number|nil, layout: string|nil, texcoord: table|nil, overlay: table|nil, textDistance: number|nil, leftPadding: number|nil, textHeight: number|nil, shortMethod: any|nil) + ---@field SetIcon fun(self: df_button,texture: string|number, width: number|nil, height: number|nil, layout: string|nil, texcoord: table|nil, overlay: table|nil, textDistance: number|nil, leftPadding: number|nil, textHeight: number|nil, shortMethod: any|nil) ---@field GetIconTexture fun(self: df_button) : string returns the texture path of the button icon ---@field SetTexture fun(self: df_button, normalTexture: string, highlightTexture: string, pressedTexture: string, disabledTexture: string) set the regular button textures ---@field SetFontFace fun(self: df_button, font: string) set the button font @@ -871,7 +877,7 @@ end ---@param func function ---@param width number ---@param height number - ---@param text string + ---@param text any ---@param param1 any|nil ---@param param2 any|nil ---@param texture any|nil @@ -887,17 +893,18 @@ end ---@return df_button function detailsFramework:NewButton(parent, container, name, member, width, height, func, param1, param2, texture, text, shortMethod, buttonTemplate, textTemplate) - if (not name) then - name = "DetailsFrameworkButtonNumber" .. detailsFramework.ButtonCounter - detailsFramework.ButtonCounter = detailsFramework.ButtonCounter + 1 - - elseif (not parent) then + if (not parent) then error("Details! FrameWork: parent not found.", 2) end - if (name:find("$parent")) then - local parentName = detailsFramework.GetParentName(parent) - name = name:gsub("$parent", parentName) + if (not name) then + local parentName = parent:GetName() + if (parentName) then + name = parentName .. "Button" .. detailsFramework.ButtonCounter + else + name = "DetailsFrameworkButtonNumber" .. detailsFramework.ButtonCounter + end + detailsFramework.ButtonCounter = detailsFramework.ButtonCounter + 1 end local buttonObject = {type = "button", dframework = true} @@ -921,7 +928,7 @@ end detailsFramework:Mixin(buttonObject.button, detailsFramework.WidgetFunctions) createButtonWidgets(buttonObject.button) - buttonObject.button:SetSize(width or 100, height or 20) + PixelUtil.SetSize(buttonObject.button, width or 100, height or 20) buttonObject.widget = buttonObject.button buttonObject.button.MyObject = buttonObject @@ -957,8 +964,8 @@ end if (shortMethod == false) then --if is false, do not use auto resize --do nothing elseif (not shortMethod) then --if the value is omitted, use the default resize - local new_width = textWidth + 15 - buttonObject.button:SetWidth(new_width) + local newWidth = textWidth + 15 + PixelUtil.SetWidth(buttonObject.button, newWidth) elseif (shortMethod == 1) then local loop = true diff --git a/libs/DF/colors.lua b/libs/DF/colors.lua index 54b5b33a..0f9b2384 100644 --- a/libs/DF/colors.lua +++ b/libs/DF/colors.lua @@ -1,13 +1,12 @@ -do +do local DF = _G ["DetailsFramework"] - if (not DF or not DetailsFrameworkCanLoad) then - return + return end DF.alias_text_colors = DF.alias_text_colors or {} - + local defaultColors = { ["HUNTER"] = {0.67, 0.83, 0.45}, ["WARLOCK"] = {0.58, 0.51, 0.79}, @@ -172,13 +171,13 @@ do ["yellow"] = {1, 1, 0, 1}, ["yellowgreen"] = {0.603922, 0.803922, 0.196078, 1} } - + function DF:GetDefaultColorList() return defaultColors end - + for colorName, colorTable in pairs(defaultColors) do - DF.alias_text_colors [colorName] = colorTable + DF.alias_text_colors[colorName] = colorTable end - + end diff --git a/libs/DF/cooltip.lua b/libs/DF/cooltip.lua index 72db42ef..c38007f0 100644 --- a/libs/DF/cooltip.lua +++ b/libs/DF/cooltip.lua @@ -15,7 +15,7 @@ local max = math.max --api locals local PixelUtil = PixelUtil or DFPixelUtil -local version = 17 +local version = 18 local CONST_MENU_TYPE_MAINMENU = "main" local CONST_MENU_TYPE_SUBMENU = "sub" @@ -2204,6 +2204,9 @@ function DF:CreateCoolTip() frame1:ClearAllPoints() PixelUtil.SetPoint(frame1, gameCooltip.OptionsTable.MyAnchor, anchor, gameCooltip.OptionsTable.RelativeAnchor, 0 + moveX + gameCooltip.OptionsTable.WidthAnchorMod, 10 + gameCooltip.OptionsTable.HeightAnchorMod + moveY) + local bHadXPositionOutOfScreen = false + local bHadYPositionOutOfScreen = false + if (not xOffset) then --check if cooltip is out of screen bounds local centerX = frame1:GetCenter() @@ -2216,13 +2219,17 @@ function DF:CreateCoolTip() --out of right side local moveLeftOffset = (centerX + halfScreenWidth) - screenWidth gameCooltip.internal_x_mod = -moveLeftOffset - return gameCooltip:SetMyPoint(host, -moveLeftOffset, 0) + xOffset = -moveLeftOffset + bHadXPositionOutOfScreen = true + --return gameCooltip:SetMyPoint(host, -moveLeftOffset, 0) elseif (centerX - halfScreenWidth < 0) then --out of left side local moveRightOffset = centerX - halfScreenWidth gameCooltip.internal_x_mod = moveRightOffset * -1 - return gameCooltip:SetMyPoint(host, moveRightOffset * -1, 0) + xOffset = moveRightOffset * -1 + bHadXPositionOutOfScreen = true + --return gameCooltip:SetMyPoint(host, moveRightOffset * -1, 0) end end end @@ -2238,17 +2245,25 @@ function DF:CreateCoolTip() --out of top side local moveDownOffset = (centerY + helpScreenHeight) - screenHeight gameCooltip.internal_y_mod = -moveDownOffset - return gameCooltip:SetMyPoint(host, 0, -moveDownOffset) + yOffset = -moveDownOffset + bHadYPositionOutOfScreen = true + --return gameCooltip:SetMyPoint(host, 0, -moveDownOffset) elseif (centerY - helpScreenHeight < 0) then --out of bottom side local moveUpOffset = centerY - helpScreenHeight gameCooltip.internal_y_mod = moveUpOffset * -1 - return gameCooltip:SetMyPoint(host, 0, moveUpOffset * -1) + yOffset = moveUpOffset * -1 + bHadYPositionOutOfScreen = true + --return gameCooltip:SetMyPoint(host, 0, moveUpOffset * -1) end end end + if (bHadXPositionOutOfScreen or bHadYPositionOutOfScreen) then + return gameCooltip:SetMyPoint(host, bHadXPositionOutOfScreen and xOffset or 0, bHadYPositionOutOfScreen and yOffset or 0) + end + if (frame2:IsShown() and not gameCooltip.overlap_checked) then local frame2CenterX = frame2:GetCenter() if (frame2CenterX) then diff --git a/libs/DF/definitions.lua b/libs/DF/definitions.lua index d25d9308..40ef74df 100644 --- a/libs/DF/definitions.lua +++ b/libs/DF/definitions.lua @@ -1,27 +1,84 @@ ---@class df_table_functions ---@field find fun(tbl:table, value:any) : number? find the index of a value in a array ----@field addunique fun(tbl:table, value:any) : boolean add a value to an array if it doesn't exist yet ----@field reverse fun(tbl:table) reverse the order of an array ----@field append fun(tbl1:table, tbl2:table) append the array of table2 to table1 ----@field duplicate fun(tblReceiving:table, tblGiving:table) copy the values from table2 to table1 overwriting existing values, ignores __index and __newindex, keys pointing to a UIObject are preserved ----@field copy fun(tblReceiving:table, tblGiving:table) copy the values from table2 to table1 overwriting existing values, ignores __index and __newindex, threat UIObjects as regular tables ----@field deploy fun(tblReceiving:table, tblGiving:table) copy keys/values that does exist on tblGiving but not in tblReceiving ----@field copytocompress fun(tblReceiving:table, tblGiving:table) copy the values from table2 to table1 overwriting existing values, ignores __index, functions and tables with a 'GetObjectType' key +---@field addunique fun(tbl:table, index:any, value:any) : boolean add a value to an array if it doesn't exist yet, if the index is omitted the value will be added to the end of the array +---@field reverse fun(tbl:table) : table reverse the order of an array +---@field append fun(tbl1:table, tbl2:table) : table append the array of table2 to table1 +---@field duplicate fun(tblReceiving:table, tblGiving:table) : table copy the values from table2 to table1 overwriting existing values, ignores __index and __newindex, keys pointing to a UIObject are preserved +---@field copy fun(tblReceiving:table, tblGiving:table) : table copy the values from table2 to table1 overwriting existing values, ignores __index and __newindex, threat UIObjects as regular tables +---@field deploy fun(tblReceiving:table, tblGiving:table) : table copy keys/values that does exist on tblGiving but not in tblReceiving +---@field copytocompress fun(tblReceiving:table, tblGiving:table) : table copy the values from table2 to table1 overwriting existing values, ignores __index, functions and tables with a 'GetObjectType' key ---@field removeduplicate fun(tbl1:table, tbl2:table) remove the keys from table1 which also exists in table2 with the same value +---@field getfrompath fun(tbl:table, path:string) : any get a value from a table using a path, e.g. getfrompath(tbl, "a.b.c") is the same as tbl.a.b.c +---@field setfrompath fun(tbl:table, path:string, value:any) : boolean set the value of a table using a path, e.g. setfrompath(tbl, "a.b.c", 10) is the same as tbl.a.b.c = 10 ---@field dump fun(tbl:table) : string dump a table to a string +---@class df_language : table +---@field Register fun(addonId:any, languageId:string, gameLanguageOnly:boolean?) : table +---@field GetLanguageTable fun(addonId:any, languageId:string?) : table +---@field GetText fun(addonId:any, phraseId:string, silent:boolean?) : string, string +---@field ShowOptionsHelp fun() +---@field SetOption fun(addonId:any, optionId:string, value:any) +---@field SetCurrentLanguage fun(addonId:any, languageId:string) +---@field CreateLanguageSelector fun(addonId:any, parent:frame, callback:function, selectedLanguage:string?) : df_dropdown +---@field SetFontForLanguageId fun(addonId:any, languageId:string, fontPath:string) +---@field SetFontByAlphabetOrRegion fun(addonId:any, latin_FontPath:string, cyrillic_FontPath:string, china_FontPath:string, korean_FontPath:string, taiwan_FontPath:string) +---@field RegisterObject fun(addonId:any, object:uiobject, phraseId:string, silent:boolean?, ...) : boolean +---@field UpdateObjectArguments fun(addonId:any, object:uiobject, ...) : boolean +---@field RegisterTableKey fun(addonId:any, table:table, key:any, phraseId:string, silent:boolean?, ...) : boolean +---@field UpdateTableKeyArguments fun(addonId:any, table:table, key:any, ...) : boolean +---@field RegisterObjectWithDefault fun(addonId:any, object:uiobject, phraseId:string, defaultText:string, ...) : boolean +---@field RegisterTableKeyWithDefault fun(addonId:any, table:table, key:any, phraseId:string, defaultText:string, ...) : boolean +---@field CreateLocTable fun(addonId:any, phraseId:string, shouldRegister:boolean?, ...) : table +---@field SetTextWithLocTable fun(object:uiobject, locTable:table) +---@field SetTextWithLocTableWithDefault fun(object:uiobject, locTable:table, defaultText:string) +---@field SetTextIfLocTableOrDefault fun(object:uiobject, locTable:table) +---@field RegisterTableKeyWithLocTable fun(table:table, key:any, locTable:table, silence:boolean?) +---@field RegisterObjectWithLocTable fun(object:uiobject, locTable:table, silence:boolean?) + +---@alias templatetype +---| "font" +---| "dropdown" +---| "button" +---| "switch" +---| "slider" + ---@class detailsframework +---@field dversion number +---@field internalFunctions table ---@field OptionsFunctions df_optionsmixin +---@field GlobalWidgetControlNames table ---@field RoundedCornerPanelMixin df_roundedcornermixin ---@field Schedules df_schedule +---@field HeaderFunctions df_headerfunctions +---@field Language df_language +---@field KeybindMixin df_keybindmixin +---@field ScriptHookMixin df_scripthookmixin +---@field EditorMixin df_editormixin +---@field ClassCache {ID:number, Name:string, FileString:string, Texture:string, TexCoord:number[]}[] only available after calling GetClassList() ---@field Math df_math +---@field FontOutlineFlags {key1:outline, key2:string}[] ---@field table df_table_functions ----@field Dispatch fun(self:table, callback:function, ...) : any dispatch a function call using xpcall +---@field AnchorPoints string[] +---@field ClassFileNameToIndex table engClass -> classIndex +---@field LoadSpellCache fun(self:table, hashMap:table, indexTable:table, allSpellsSameName:table) : hashMap:table, indexTable:table, allSpellsSameName:table load all spells in the game and add them into the passed tables +---@field UnloadSpellCache fun(self:table) wipe the table contents filled with LoadSpellCache() +---@field GetCurrentClassName fun(self:table) : string return the name of the class the player is playing +---@field GetCurrentSpecName fun(self:table) : string return the name of the spec the player is playing +---@field GetSpellCaches fun(self:table) : table, table, table return the tables filled with LoadSpellCache() +---@field GetCurrentSpec fun(self:table):number? +---@field GetCurrentSpecId fun(self:table):number? return the specId of the current spec, retuns nil if the expansion the player is playing does not support specs +---@field GetClassSpecIds fun(self:table, engClass:string):number[] +---@field IsValidSpecId fun(self:table, specId:number):boolean check if the passed specId is valid for the player class, also return false for tutorial specs +---@field GetClassList fun(self:table):{ID:number, Name:string, FileString:string, Texture:string, TexCoord:number[]}[] +---@field DebugVisibility fun(self:table, object:uiobject) print the reason why the frame isn't shown in the screen +---@field Dispatch fun(self:table, callback:function, ...) : any dispatch a function call using xpcall, print to chat if the function passed is invalid +---@field QuickDispatch fun(self:table, callback:function, ...) : any dispatch a function call without errors if the function passed is invalid +---@field CoreDispatch fun(self:table, context:string, callback:function, ...) : any dispatch a function using xpcall, make an error if the function passed is invalid ---@field GetDefaultBackdropColor fun(self:table) : red, green, blue, alpha return the standard backdrop color used by blizzard on their own frames ---@field Msg fun(self:table, message:string, ...) show a message in the chat frame ---@field MsgWarning fun(self:table, message:string, ...) show a warning message in the chat frame ----@field CreateButton fun(self:table, parent:frame, func:function, width:number, height:number, text:string?, param1:any, param2:any, texture:atlasname|texturepath|textureid|nil, member:string?, name:string?, shortMethod:any, buttonTemplate:table?, textTemplate:table?) : df_button +---@field CreateButton fun(self:table, parent:frame, func:function, width:number, height:number, text:any, param1:any, param2:any, texture:atlasname|texturepath|textureid|nil, member:string?, name:string?, shortMethod:any, buttonTemplate:table?, textTemplate:table?) : df_button ---@field CreateCloseButton fun(self:table, parent:frame, frameName:string?) : df_closebutton ---@field CreateTabButton fun(self:table, parent:frame, frameName:string?) : df_tabbutton ---@field CreateRoundedPanel fun(self:table, parent:frame, frameName:string?, optionsTable:df_roundedpanel_options?) : df_roundedpanel @@ -32,8 +89,10 @@ ---@field SetButtonTexture fun(self:table, button:button|df_button, texture:atlasname|texturepath|textureid) ---@field CreateFadeAnimation fun(self:table, UIObject:uiobject, fadeInTime:number?, fadeOutTime:number?, fadeInAlpha:number?, fadeOutAlpha:number?) ---@field SetFontSize fun(self:table, fontstring:fontstring, size:number) +---@field GetFontSize fun(self:table, fontstring:fontstring) : number return the font size of the fontstring ---@field SetFontColor fun(self:table, fontstring:fontstring, red:any, green:number?, blue:number?, alpha:number?) ---@field SetFontFace fun(self:table, fontstring:fontstring, font:string) +---@field GetFontFace fun(self:table, fontstring:fontstring) : string return the font face of the fontstring ---@field SetFontShadow fun(self:table, fontstring:fontstring, red:any, green:number?, blue:number?, alpha:number?, offsetX:number?, offsetY:number?) ---@field SetFontOutline fun(self:table, fontstring:fontstring, outline:fontflags) ---@field RemoveRealmName fun(self:table, name:string) : string, number remove the realm name from the player name, must be in the format of "name-realm" @@ -44,7 +103,40 @@ ---@field CommaValue fun(self:table, value:number) : string convert a number to a string with commas, e.g. 1000000 -> 1,000,000 ---@field SplitTextInLines fun(self:table, text:string) : string[] split a text into lines ---@field UnitGroupRolesAssigned fun(unitId: unit, bUseSupport:boolean, specId: specializationid) : string there's no self here ----@field SetAnchor fun(self:table, widget:uiobject, anchorTable:df_anchor, anchorTo:uiobject) ----@field +---@field SetAnchor fun(self:table, widget:uiobject, anchorTable:df_anchor, anchorTo:uiobject) only adjust the anchors of a widget, does not save values +---@field AddTextureToText fun(text:string, textureInfo:table, bAddSpace:boolean?, bAddAfterText:boolean) : string textureInfo is a table with .texture .width .height .coords{left, right, top, bottom} +---@field CreateTextureInfo fun(texture:atlasname|texturepath|textureid, width:number?, height:number?, left:number?, right:number?, top:number?, bottom:number?, imageWidthnumber?, imageHeightnumber?) : table +---@field ApplyStandardBackdrop fun(self:table, frame:frame, bUseSolidColor:boolean?, alphaScale:number?) +---@field CreateLabel fun(self:table, parent:frame, text:string, size:number?, color:any?, font:string?, member:string?, name:string?, layer:drawlayer?) : df_label +---@field CreateDropDown fun(self:table, parent:frame, func:function, default:any, width:number?, height:number?, member:string?, name:string?, template:table?) : df_dropdown +---@field CreateFontDropDown fun(self:table, parent:frame, func:function, default:any, width:number?, height:number?, member:string?, name:string?, template:table?) : df_dropdown +---@field CreateColorDropDown fun(self:table, parent:frame, func:function, default:any, width:number?, height:number?, member:string?, name:string?, template:table?) : df_dropdown +---@field CreateFontListGenerator fun(self:table, callback:function) : function return a function which when called returns a table filled with all fonts available and ready to be used on dropdowns +---@field CreateTextEntry fun(self:table, parent:frame, textChangedCallback:function, width:number, height:number, member:string?, name:string?, labelText:string?, textentryTemplate:table?, labelTemplate:table?) : df_textentry +---@field ReskinSlider fun(self:table, slider:frame) +---@field GetAvailableSpells fun(self:table) : table +---@field NewColor fun(self:table, colorName:string, red:number, green:number, blue:number, alpha:number) +---@field CreateKeybindFrame fun(self:table, parent:frame, name:string?, options:table?, setKeybindCallback:function?, keybindData:table?) : df_keybindframe +---@field CreateStatusBar fun(self:table, parent:frame, options:table?) : frame +---@field GetTemplate fun(self:table, templateType:templatetype, templateName:string) : table +---@field UpdateLoadConditionsTable fun(self:table, loadConditionsTable:table) +---@field IconPick fun(self:table, callback:function, bCloseWhenSelect:boolean?, param1:any?, param2:any?) +---@field OpenLoadConditionsPanel fun(self:table, optionsTable:table, callback:function, frameOptions:table?) +---@field InstallTemplate fun(self:table, widgetType:string, templateName:string, template:table, parentName:any) : table +---@field NewSpecialLuaEditorEntry fun(self:table, parent:frame, width:number, height:number, member:string?, name:string?, nointent:boolean?, showLineNumbers:boolean?, bNoName:boolean?) : df_luaeditor +---@field PassLoadFilters fun(self:table, loadTable:table, encounterID:number?) : boolean, string +---@field CreateLoadFilterParser fun(self:table, callback:fun(encounterId:number?)) create a helper which will callback when encounterId, spec, talent, role, combatstate changes +---@field CreateSwitch fun(self:table, parent:frame, onSwitch:function, defaultValue:boolean, width:number?, height:number?, leftText:string?, rightText:string?, member:string?, name:string?, colorInverted:boolean?, switchFunc:function?, returnFunc:function?, withLabel:string?, switch_template:table?, label_template:table?) : df_checkbox, df_label? +---@field CreateCheckboxGroup fun(self:table, parent:frame, radioOptions:df_radiooptions[], name:string?, options:table?, anchorOptions:table?) : df_checkboxgroup +---@field CreateRadioGroup fun(self:table, parent:frame, radioOptions:df_radiooptions[], name:string?, options:table?, anchorOptions:table?) : df_radiogroup +---@field CreateScrollBox fun(self:table, parent:frame, name:string, refreshFunc:function, data:table, width:number, height:number, lineAmount:number, lineHeight:number, createLineFunc:function?, autoAmount:boolean?, noScroll:boolean?, noBackdrop:boolean?) : df_scrollbox +---@field CreateAuraScrollBox fun(self:table, parent:frame, name:string?, data:table?, onRemoveCallback:function?, options:table?) : df_aurascrollbox +---@field CreateGridScrollBox fun(self:table, parent:frame, name:string?, refreshFunc:function, data:table?, createColumnFrameFunc:function, options:table?) : df_gridscrollbox +---@field CreateCanvasScrollBox fun(self:table, parent:frame, child:frame?, name:string?, options:table?) : df_canvasscrollbox +---@field CreateTabContainer fun(self:table, parent:frame, title:string, frameName:string, tabList:df_tabinfotable[], optionsTable:table?, hookList:table?, languageInfo:table?) : df_tabcontainer +---@field GetSizeFromPercent fun(self:table, uiObject:uiobject, percent:number) : number get the min size of a uiObject and multiply it by the percent passed +---@field BuildMenu fun(self:table, parent:frame, menuOptions:df_menu_table[], xOffset:number?, yOffset:number?, height:number?, useColon:boolean?, textTemplate:table?, dropdownTemplate:table?, switchTemplate:table?, switchIsCheckbox:boolean?, sliderTemplate:table?, buttonTemplate:table?, valueChangeHook:function?) +---@field BuildMenuVolatile fun(self:table, parent:frame, menuOptions:df_menu_table[], xOffset:number?, yOffset:number?, height:number?, useColon:boolean?, textTemplate:table?, dropdownTemplate:table?, switchTemplate:table?, switchIsCheckbox:boolean?, sliderTemplate:table?, buttonTemplate:table?, valueChangeHook:function?) ---@field ---@field + diff --git a/libs/DF/dropdown.lua b/libs/DF/dropdown.lua index bf16cb99..69928474 100644 --- a/libs/DF/dropdown.lua +++ b/libs/DF/dropdown.lua @@ -989,6 +989,10 @@ end function DF:BuildDropDownFontList(onClick, icon, iconTexcoord, iconSize) local fontTable = {} + if (type(iconSize) ~= "table") then + iconSize = {iconSize or 16, iconSize or 16} + end + local SharedMedia = LibStub:GetLibrary("LibSharedMedia-3.0") for name, fontPath in pairs(SharedMedia:HashTable("font")) do fontTable[#fontTable+1] = {value = name, label = name, onclick = onClick, icon = icon, iconsize = iconSize, texcoord = iconTexcoord, font = fontPath, descfont = "abcdefg ABCDEFG"} @@ -1006,10 +1010,10 @@ function DropDownMetaFunctions:SetTemplate(template) self.template = template if (template.width) then - self:SetWidth(template.width) + PixelUtil.SetWidth(self.dropdown, template.width) end if (template.height) then - self:SetHeight(template.height) + PixelUtil.SetWidth(self.dropdown, template.height) end if (template.backdrop) then @@ -1083,6 +1087,136 @@ end ------------------------------------------------------------------------------------------------------------ --object constructor +---@class df_dropdown : table, frame +---@field SetTemplate fun(self:df_dropdown, template:table) +---@field BuildDropDownFontList fun(self:df_dropdown, onClick:function, icon:any, iconTexcoord:table?, iconSize:table?):table make a dropdown list with all fonts available, on select a font, call the function onClick +---@field +---@field +---@field +---@field +---@field + +---return a function which when called returns a table filled with all fonts available and ready to be used on dropdowns +---@param callback function +---@return function +function DF:CreateFontListGenerator(callback) + return function() return DF:BuildDropDownFontList(callback, [[Interface\AnimCreate\AnimCreateIcons]], {0, 32/128, 64/128, 96/128}, 16) end +end + +local colorGeneratorStatusBarTexture = [[Interface\Tooltips\UI-Tooltip-Background]] +local colorGeneratorStatusBarColor = {.1, .1, .1, .8} +local colorGeneratorNoColor = {0, 0, 0, 0} + +function DF:CreateColorListGenerator(callback) + local newGenerator = function() + local dropdownOptions = {} + + for colorName, colorTable in pairs(DF:GetDefaultColorList()) do + table.insert(dropdownOptions, { + label = colorName, + value = colorTable, + color = colorTable, + statusbar = colorGeneratorStatusBarTexture, + statusbarcolor = colorGeneratorStatusBarColor, + onclick = callback + }) + end + + table.insert(dropdownOptions, 1, { + label = "no color", + value = "blank", + color = colorGeneratorNoColor, + statusbar = colorGeneratorStatusBarTexture, + statusbarcolor = colorGeneratorStatusBarColor, + onclick = callback + }) + + return dropdownOptions + end + + return newGenerator +end + +function DF:CreateOutlineListGenerator(callback) + local newGenerator = function() + local dropdownOptions = {} + + for i, outlineInfo in ipairs(DF.FontOutlineFlags) do + local outlineName, outlineLoc = unpack(outlineInfo) + table.insert(dropdownOptions, { + label = outlineLoc, + value = outlineName, + onclick = callback + }) + end + + return dropdownOptions + end + + return newGenerator +end + +function DF:CreateAnchorPointListGenerator(callback) + local newGenerator = function() + local dropdownOptions = {} + + for i, pointName in pairs(DF.AnchorPoints) do + table.insert(dropdownOptions, { + label = pointName, + value = i, + onclick = callback + }) + end + + return dropdownOptions + end + + return newGenerator +end + +---create a dropdown object with a list of fonts +---@param parent frame +---@param callback function +---@param default any +---@param width number? +---@param height number? +---@param member string? +---@param name string? +---@param template table? +function DF:CreateFontDropDown(parent, callback, default, width, height, member, name, template) + local func = DF:CreateFontListGenerator(callback) + local dropDownObject = DF:NewDropDown(parent, parent, name, member, width, height, func, default, template) + return dropDownObject +end + +function DF:CreateColorDropDown(parent, callback, default, width, height, member, name, template) + local func = DF:CreateColorListGenerator(callback) + local dropDownObject = DF:NewDropDown(parent, parent, name, member, width, height, func, default, template) + return dropDownObject +end + +function DF:CreateOutlineDropDown(parent, callback, default, width, height, member, name, template) + local func = DF:CreateOutlineListGenerator(callback) + local dropDownObject = DF:NewDropDown(parent, parent, name, member, width, height, func, default, template) + return dropDownObject +end + +function DF:CreateAnchorPointDropDown(parent, callback, default, width, height, member, name, template) + local func = DF:CreateAnchorPointListGenerator(callback) + local dropDownObject = DF:NewDropDown(parent, parent, name, member, width, height, func, default, template) + return dropDownObject +end + +---create a dropdown object +---@param parent frame +---@param func function +---@param default any +---@param width number? +---@param height number? +---@param member string? +---@param name string? +---@param template table? +---@return df_dropdown function DF:CreateDropDown(parent, func, default, width, height, member, name, template) return DF:NewDropDown(parent, parent, name, member, width, height, func, default, template) end @@ -1123,8 +1257,7 @@ function DF:NewDropDown(parent, container, name, member, width, height, func, de end dropDownObject.dropdown = DF:CreateNewDropdownFrame(parent, name) - dropDownObject.dropdown:SetWidth(width) - dropDownObject.dropdown:SetHeight(height) + PixelUtil.SetSize(dropDownObject.dropdown, width, height) dropDownObject.container = container dropDownObject.widget = dropDownObject.dropdown @@ -1313,7 +1446,7 @@ function DF:CreateNewDropdownFrame(parent, name) child.mouseover = mouseover scroll:SetScrollChild(child) - tinsert(UISpecialFrames, newDropdownFrame.dropdownborder:GetName()) + table.insert(UISpecialFrames, newDropdownFrame.dropdownborder:GetName()) --tinsert(UISpecialFrames, f.dropdownframe:GetName()) --not adding this solves an issue with ConsolePort addon and stackoverflows on Hide... return newDropdownFrame diff --git a/libs/DF/editor.lua b/libs/DF/editor.lua new file mode 100644 index 00000000..d1e2f072 --- /dev/null +++ b/libs/DF/editor.lua @@ -0,0 +1,649 @@ + +local detailsFramework = _G["DetailsFramework"] +if (not detailsFramework or not DetailsFrameworkCanLoad) then + return +end + +---@cast detailsFramework detailsframework + +local CreateFrame = CreateFrame +local unpack = unpack +local wipe = table.wipe +local _ + +--[=[ + file description: this file has the code for the object editor + the object editor itself is a frame and has a scrollframe as canvas showing another frame where there's the options for the editing object + +--]=] + + +--the editor doesn't know which key in the profileTable holds the current value for an attribute, so it uses a map table to find it. +--the mapTable is a table with the attribute name as a key, and the value is the profile key. For example, {["size"] = "text_size"} means profileTable["text_size"] = 10. + +---@class df_editor_attribute +---@field name string +---@field label string +---@field widget string +---@field default any +---@field minvalue number? +---@field maxvalue number? +---@field step number? +---@field usedecimals boolean? +---@field subkey string? + +--which object attributes are used to build the editor menu for each object type +local attributes = { + ---@type df_editor_attribute[] + FontString = { + { + name = "text", + label = "Text", + widget = "textentry", + default = "font string text", + setter = function(widget, value) widget:SetText(value) end, + }, + { + name = "size", + label = "Size", + widget = "range", + minvalue = 5, + maxvalue = 120, + setter = function(widget, value) widget:SetFont(widget:GetFont(), value, select(3, widget:GetFont())) end + }, + { + name = "font", + label = "Font", + widget = "fontdropdown", + setter = function(widget, value) + local font = LibStub:GetLibrary("LibSharedMedia-3.0"):Fetch("font", value) + widget:SetFont(font, select(2, widget:GetFont())) + end + }, + { + name = "color", + label = "Color", + widget = "color", + setter = function(widget, value) widget:SetTextColor(unpack(value)) end + }, + { + name = "alpha", + label = "Alpha", + widget = "range", + setter = function(widget, value) widget:SetAlpha(value) end + }, + { + name = "shadow", + label = "Draw Shadow", + widget = "toggle", + setter = function(widget, value) widget:SetShadowColor(widget:GetShadowColor(), select(2, widget:GetShadowColor()), select(3, widget:GetShadowColor()), value and 0.5 or 0) end + }, + { + name = "shadowcolor", + label = "Shadow Color", + widget = "color", + setter = function(widget, value) widget:SetShadowColor(unpack(value)) end + }, + { + name = "shadowoffsetx", + label = "Shadow X Offset", + widget = "range", + minvalue = -10, + maxvalue = 10, + setter = function(widget, value) widget:SetShadowOffset(value, select(2, widget:GetShadowOffset())) end + }, + { + name = "shadowoffsety", + label = "Shadow Y Offset", + widget = "range", + minvalue = -10, + maxvalue = 10, + setter = function(widget, value) widget:SetShadowOffset(widget:GetShadowOffset(), value) end + }, + { + name = "outline", + label = "Outline", + widget = "outlinedropdown", + setter = function(widget, value) widget:SetFont(widget:GetFont(), select(2, widget:GetFont()), value) end + }, + { + name = "anchor", + label = "Anchor", + widget = "anchordropdown", + setter = function(widget, value) detailsFramework:SetAnchor(widget, value, widget:GetParent()) end + }, + { + name = "anchoroffsetx", + label = "Anchor X Offset", + widget = "range", + minvalue = -100, + maxvalue = 100, + setter = function(widget, value) detailsFramework:SetAnchor(widget, value, widget:GetParent()) end + }, + { + name = "anchoroffsety", + label = "Anchor Y Offset", + widget = "range", + minvalue = -100, + maxvalue = 100, + setter = function(widget, value) detailsFramework:SetAnchor(widget, value, widget:GetParent()) end + }, + { + name = "rotation", + label = "Rotation", + widget = "range", + usedecimals = true, + minvalue = 0, + maxvalue = math.pi*2, + setter = function(widget, value) widget:SetRotation(value) end + }, + } +} + +---@class df_editormixin : table +---@field GetEditingObject fun(self:df_editor):uiobject +---@field GetEditingOptions fun(self:df_editor):df_editobjectoptions +---@field GetExtraOptions fun(self:df_editor):table +---@field GetEditingProfile fun(self:df_editor):table, table +---@field GetOnEditCallback fun(self:df_editor):function +---@field GetOptionsFrame fun(self:df_editor):frame +---@field GetCanvasScrollBox fun(self:df_editor):df_canvasscrollbox +---@field EditObject fun(self:df_editor, object:uiobject, profileTable:table, profileKeyMap:table, extraOptions:table?, callback:function?, options:df_editobjectoptions?) +---@field PrepareObjectForEditing fun(self:df_editor) +---@field CreateMoverGuideLines fun(self:df_editor) +---@field GetOverTheTopFrame fun(self:df_editor):frame +---@field GetMoverFrame fun(self:df_editor):frame +---@field StartObjectMovement fun(self:df_editor, anchorSettings:df_anchor) +---@field StopObjectMovement fun(self:df_editor) + +---@class df_editobjectoptions : table +---@field use_colon boolean if true a colon is shown after the option name +---@field can_move boolean if true the object can be moved +---@field use_guide_lines boolean if true guide lines are shown when the object is being moved + +---@type df_editobjectoptions +local editObjectDefaultOptions = { + use_colon = true, + can_move = true, + use_guide_lines = true, +} + +local getParentTable = function(profileTable, profileKey) + local parentPath + if (profileKey:match("%]$")) then + parentPath = profileKey:gsub("%s*%[.*%]%s*$", "") + else + parentPath = profileKey:gsub("%.[^.]*$", "") + end + + local parentTable = detailsFramework.table.getfrompath(profileTable, parentPath) + return parentTable +end + +detailsFramework.EditorMixin = { + ---@param self df_editor + GetEditingObject = function(self) + return self.editingObject + end, + + ---@param self df_editor + ---@return df_editobjectoptions + GetEditingOptions = function(self) + return self.editingOptions + end, + + ---@param self df_editor + ---@return table + GetExtraOptions = function(self) + return self.editingExtraOptions + end, + + ---@param self df_editor + ---@return table, table + GetEditingProfile = function(self) + return self.editingProfileTable, self.editingProfileMap + end, + + ---@param self df_editor + ---@return function + GetOnEditCallback = function(self) + return self.onEditCallback + end, + + GetOptionsFrame = function(self) + return self.optionsFrame + end, + + GetOverTheTopFrame = function(self) + return self.overTheTopFrame + end, + + GetMoverFrame = function(self) + return self.moverFrame + end, + + GetCanvasScrollBox = function(self) + return self.canvasScrollBox + end, + + ---@param self df_editor + ---@param object uiobject + ---@param profileTable table + ---@param profileKeyMap table + ---@param extraOptions table? a way to add more options other than the default attributes for the object. + ---@param callback function? calls when an attribute is changed with the payload: editingObject, optionName, newValue, profileTable, profileKey + ---@param options df_editobjectoptions? + EditObject = function(self, object, profileTable, profileKeyMap, extraOptions, callback, options) + assert(type(object) == "table", "EditObject(object) expects an UIObject on first parameter.") + assert(type(profileTable) == "table", "EditObject(object) expects a table on second parameter.") + assert(object.GetObjectType, "EditObject(object) expects an UIObject on first parameter.") + + --clear previous values + self.editingObject = nil + self.editingProfileMap = nil + self.editingProfileTable = nil + self.editingOptions = nil + self.editingExtraOptions = nil + self.onEditCallback = nil + + --deploy the options table + options = type(options) == "table" and options or {} + detailsFramework.table.deploy(options, editObjectDefaultOptions) + + --as there's no other place which this members are set, there is no need to create setter functions + self.editingObject = object + self.editingProfileMap = profileKeyMap + self.editingProfileTable = profileTable + self.editingOptions = options + self.editingExtraOptions = extraOptions or {} + + if (type(callback) == "function") then + self.onEditCallback = callback + elseif (callback) then + error("EditObject(object) callback must be a function or nil.") + end + + self:PrepareObjectForEditing() + end, + + ---@param self df_editor + CreateMoverGuideLines = function(self) + local overTheTopFrame = self:GetOverTheTopFrame() + local moverFrame = self:GetMoverFrame() + + self.moverGuideLines = { + left = overTheTopFrame:CreateTexture(nil, "overlay"), + right = overTheTopFrame:CreateTexture(nil, "overlay"), + top = overTheTopFrame:CreateTexture(nil, "overlay"), + bottom = overTheTopFrame:CreateTexture(nil, "overlay"), + } + + for side, texture in pairs(self.moverGuideLines) do + texture:SetColorTexture(.8, .8, .8, 0.1) + texture:SetSize(1, 1) + texture:SetDrawLayer("overlay", 7) + texture:Hide() + + if (side == "left" or side == "right") then + texture:SetHeight(1) + texture:SetWidth(GetScreenWidth()) + else + texture:SetWidth(1) + texture:SetHeight(GetScreenHeight()) + end + end + end, + + UpdateGuideLinesAnchors = function(self) + local object = self:GetEditingObject() + + for side, texture in pairs(self.moverGuideLines) do + texture:ClearAllPoints() + if (side == "left" or side == "right") then + if (side == "left") then + texture:SetPoint("right", object, "left", -2, 0) + else + texture:SetPoint("left", object, "right", 2, 0) + end + else + if (side == "top") then + texture:SetPoint("bottom", object, "top", 0, 2) + else + texture:SetPoint("top", object, "bottom", 0, -2) + end + end + end + end, + + PrepareObjectForEditing = function(self) + --get the object and its profile table with the current values + local object = self:GetEditingObject() + local profileTable, profileMap = self:GetEditingProfile() + profileMap = profileMap or {} + + if (not object or not profileTable) then + return + end + + --get the object type + local objectType = object:GetObjectType() + local attributeList + + --get options and extra options + local editingOptions = self:GetEditingOptions() + local extraOptions = self:GetExtraOptions() + + --get the attribute list for the object type + if (objectType == "FontString") then + ---@cast object fontstring + attributeList = attributes[objectType] + end + + --if there's extra options, add the attributeList to a new table and right after the extra options + if (extraOptions) then + local attributeListWithExtraOptions = {} + + for i = 1, #attributeList do + attributeListWithExtraOptions[#attributeListWithExtraOptions+1] = attributeList[i] + end + + for i = 1, #extraOptions do + attributeListWithExtraOptions[#attributeListWithExtraOptions+1] = extraOptions[i] + end + + attributeList = attributeListWithExtraOptions + end + + local anchorSettings + + --table to use on DF:BuildMenu() + local menuOptions = {} + for i = 1, #attributeList do + local option = attributeList[i] + + --get the key to be used on profile table + local profileKey = profileMap[option.name] + local value + + --if the key contains a dot or a bracket, it means it's a table path, example: "text_settings[1].width" + if (profileKey and (profileKey:match("%.") or profileKey:match("%["))) then + value = detailsFramework.table.getfrompath(profileTable, profileKey) + else + value = profileTable[profileKey] + end + + --if no value is found, attempt to get a default + value = value or option.default + + local minValue = option.minvalue + local maxValue = option.maxvalue + + if (option.name == "anchoroffsetx") then + minValue = -object:GetParent():GetWidth()/2 + maxValue = object:GetParent():GetWidth()/2 + elseif (option.name == "anchoroffsety") then + minValue = -object:GetParent():GetHeight()/2 + maxValue = object:GetParent():GetHeight()/2 + end + + if (value) then + local parentTable = getParentTable(profileTable, profileKey) + + if (option.name == "anchor" or option.name == "anchoroffsetx" or option.name == "anchoroffsety") then + anchorSettings = parentTable + end + + menuOptions[#menuOptions+1] = { + type = option.widget, + name = option.label, + get = function() return value end, + set = function(widget, fixedValue, newValue, ...) + --color is a table with 4 indexes for each color plus alpha + if (option.name:find("color")) then + --calor callback sends the red color in the fixedParameter slot + local r, g, b, alpha = fixedValue, newValue, ... + --need to use the same table from the profile table + parentTable[1] = r + parentTable[2] = g + parentTable[3] = b + parentTable[4] = alpha + + newValue = parentTable + else + detailsFramework.table.setfrompath(profileTable, profileKey, newValue) + end + + if (self:GetOnEditCallback()) then + self:GetOnEditCallback()(object, option.name, newValue, profileTable, profileKey) + end + + --update the widget visual + --anchoring uses SetAnchor() which require the anchorTable to be passed + if (option.name == "anchor" or option.name == "anchoroffsetx" or option.name == "anchoroffsety") then + anchorSettings = parentTable + + if (option.name == "anchor") then + anchorSettings.x = 0 + anchorSettings.y = 0 + end + + self:StopObjectMovement() + + option.setter(object, parentTable) + + if (editingOptions.can_move) then + self:StartObjectMovement(anchorSettings) + end + else + option.setter(object, newValue) + end + end, + min = minValue, + max = maxValue, + step = option.step, + usedecimals = option.usedecimals, + id = option.name, + } + end + end + + --at this point, the optionsTable is ready to be used on DF:BuildMenuVolatile() + menuOptions.align_as_pairs = true + menuOptions.align_as_pairs_length = 150 + menuOptions.widget_width = 180 + + local optionsFrame = self:GetOptionsFrame() + local canvasScrollBox = self:GetCanvasScrollBox() + + local bUseColon = editingOptions.use_colon + + local bSwitchIsCheckbox = true + local maxHeight = 5000 + + local amountOfOptions = #menuOptions + local optionsFrameHeight = amountOfOptions * 20 + optionsFrame:SetHeight(optionsFrameHeight) + + --templates + local options_text_template = detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE") + local options_dropdown_template = detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE") + local options_switch_template = detailsFramework:GetTemplate("switch", "OPTIONS_CHECKBOX_TEMPLATE") + local options_slider_template = detailsFramework:GetTemplate("slider", "OPTIONS_SLIDER_TEMPLATE") + local options_button_template = detailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE") + + detailsFramework:BuildMenu(optionsFrame, menuOptions, 0, -2, maxHeight, bUseColon, options_text_template, options_dropdown_template, options_switch_template, bSwitchIsCheckbox, options_slider_template, options_button_template) + + if (editingOptions.can_move) then + self:StartObjectMovement(anchorSettings) + end + end, + + ---@param self df_editor + ---@param anchorSettings df_anchor + StartObjectMovement = function(self, anchorSettings) + local object = self:GetEditingObject() + local moverFrame = self:GetMoverFrame() + + moverFrame:EnableMouse(true) + moverFrame:SetMovable(true) + moverFrame:ClearAllPoints() + moverFrame:SetPoint("topleft", object, "topleft", -4, 4) + moverFrame:SetPoint("bottomright", object, "bottomright", 4, -4) + moverFrame:Show() + + self:UpdateGuideLinesAnchors() + + --update the mover frame size to match the object size + if (object:GetObjectType() == "FontString") then + ---@cast object fontstring + local width = object:GetStringWidth() + local height = object:GetStringHeight() + moverFrame:SetSize(width, height) + else + local width, height = object:GetSize() + moverFrame:SetSize(width, height) + end + + moverFrame:SetScript("OnMouseDown", function() + moverFrame:StartMoving() + moverFrame.bIsMoving = true + end) + + moverFrame:SetScript("OnMouseUp", function() + moverFrame:StopMovingOrSizing() + moverFrame.bIsMoving = false + end) + + --update guidelines + if (self:GetEditingOptions().use_guide_lines) then + --show all four guidelines + for side, texture in pairs(self.moverGuideLines) do + texture:Show() + end + end + + local optionsFrame = self:GetOptionsFrame() + + --record the current position of the moverFrame to check later if the frame has moved + --if the moverFrame has moved, need to update the anchor settings x and y values + local currentPosX, currentPosY = moverFrame:GetCenter() + + moverFrame:SetScript("OnUpdate", function() + --if the object isn't moving, make the mover follow the object position + if (moverFrame.bIsMoving) then + --if the object is moving, check if the moverFrame has moved + local newPosX, newPosY = moverFrame:GetCenter() + + --did the frame moved? + if (newPosX ~= currentPosX or newPosY ~= currentPosY) then + --if the moverFrame has moved, update the anchor settings + local xOffset = newPosX - currentPosX + local yOffset = newPosY - currentPosY + anchorSettings.x = anchorSettings.x + xOffset + anchorSettings.y = anchorSettings.y + yOffset + + --update the anchor x and y slider's shown value without calling the callback + local anchorXSlider = optionsFrame:GetWidgetById("anchoroffsetx") + anchorXSlider:SetValueNoCallback(anchorSettings.x) + local anchorYSlider = optionsFrame:GetWidgetById("anchoroffsety") + anchorYSlider:SetValueNoCallback(anchorSettings.y) + + --update the object anchor + detailsFramework:SetAnchor(object, anchorSettings, object:GetParent()) + + --update the current position + currentPosX, currentPosY = newPosX, newPosY + end + else + moverFrame:ClearAllPoints() + moverFrame:SetPoint("topleft", object, "topleft", -4, 4) + moverFrame:SetPoint("bottomright", object, "bottomright", 4, -4) + end + + --update the mover frame size to match the object size + if (object:GetObjectType() == "FontString") then + ---@cast object fontstring + local width = object:GetStringWidth() + local height = object:GetStringHeight() + moverFrame:SetSize(width, height) + else + local width, height = object:GetSize() + moverFrame:SetSize(width, height) + end + end) + end, + + ---@param self df_editor + StopObjectMovement = function(self) + local moverFrame = self:GetMoverFrame() + + moverFrame:EnableMouse(false) + moverFrame:SetScript("OnUpdate", nil) + + --hide all four guidelines + for side, texture in pairs(self.moverGuideLines) do + texture:Hide() + end + + moverFrame:Hide() + end, +} + +local editorDefaultOptions = { + width = 400, + height = 600, +} + +---@class df_editor : frame, df_optionsmixin, df_editormixin +---@field options table +---@field editingObject uiobject +---@field editingProfileTable table +---@field editingProfileMap table +---@field editingOptions df_editobjectoptions +---@field editingExtraOptions table +---@field onEditCallback function +---@field optionsFrame frame +---@field overTheTopFrame frame +---@field moverFrame frame +---@field moverGuideLines table +---@field canvasScrollBox df_canvasscrollbox + +function detailsFramework:CreateEditor(parent, name, options) + name = name or ("DetailsFrameworkEditor" .. math.random(100000, 10000000)) + local editorFrame = CreateFrame("frame", name, parent, "BackdropTemplate") + + detailsFramework:Mixin(editorFrame, detailsFramework.EditorMixin) + detailsFramework:Mixin(editorFrame, detailsFramework.OptionsFunctions) + + editorFrame:BuildOptionsTable(editorDefaultOptions, options) + + editorFrame:SetSize(editorFrame.options.width, editorFrame.options.height) + + --options frame is the frame that holds the options for the editing object, it is used as the parent frame for BuildMenuVolatile() + local optionsFrame = CreateFrame("frame", name .. "OptionsFrame", editorFrame, "BackdropTemplate") + optionsFrame:SetSize(editorFrame.options.width, 5000) + + local canvasFrame = detailsFramework:CreateCanvasScrollBox(editorFrame, optionsFrame, name .. "CanvasScrollBox") + canvasFrame:SetAllPoints() + + --over the top frame is a frame that is always on top of everything else + local OTTFrame = CreateFrame("frame", "$parentOTTFrame", UIParent) + OTTFrame:SetFrameStrata("TOOLTIP") + editorFrame.overTheTopFrame = OTTFrame + + --frame that is used to move the object + local moverFrame = CreateFrame("frame", "$parentMoverFrame", OTTFrame, "BackdropTemplate") + moverFrame:SetClampedToScreen(true) + moverFrame:SetBackdrop({ + edgeFile = "Interface\\Buttons\\WHITE8x8", + edgeSize = 1, + }) + editorFrame.moverFrame = moverFrame + + editorFrame:CreateMoverGuideLines() + + editorFrame.optionsFrame = optionsFrame + editorFrame.canvasScrollBox = canvasFrame + + return editorFrame +end diff --git a/libs/DF/frames.lua b/libs/DF/frames.lua index fc3d128d..4b5f698f 100644 --- a/libs/DF/frames.lua +++ b/libs/DF/frames.lua @@ -37,11 +37,6 @@ local cornerNames = {"TopLeft", "TopRight", "BottomLeft", "BottomRight"} ---@field Left texture ---@field Right texture ----@class df_roundedpanel_preset : table ----@field border_color any ----@field color any ----@field roundness number - ---@class df_roundedpanel_options : table ---@field width number ---@field height number @@ -53,6 +48,12 @@ local cornerNames = {"TopLeft", "TopRight", "BottomLeft", "BottomRight"} ---@field color any ---@field border_color any ---@field corner_texture texturepath|textureid +---@field horizontal_border_size_offset number? + +---@class df_roundedpanel_preset : table, df_roundedpanel_options +---@field border_color any +---@field color any +---@field roundness number ---@class df_roundedcornermixin : table ---@field RoundedCornerConstructor fun(self:df_roundedpanel) --called from CreateRoundedPanel @@ -293,13 +294,13 @@ detailsFramework.RoundedCornerPanelMixin = { --set the new size of the corners on all corner textures for _, thisTexture in pairs(self.CornerTextures) do - thisTexture:SetSize(newCornerSize-self.cornerRoundness, newCornerSize) + thisTexture:SetSize(newCornerSize - (self.cornerRoundness - 2), newCornerSize) end --check if the frame has border and set the size of the border corners as well if (self.bHasBorder) then for _, thisTexture in pairs(self.BorderCornerTextures) do - thisTexture:SetSize(newCornerSize, newCornerSize) + thisTexture:SetSize(newCornerSize-2, newCornerSize+2) end --hide the left and right edges as the corner textures already is enough to fill the frame @@ -307,8 +308,8 @@ detailsFramework.RoundedCornerPanelMixin = { self.BorderEdgeTextures["Right"]:Hide() local horizontalEdgesNewSize = self:CalculateBorderEdgeSize("horizontal") - self.BorderEdgeTextures["Top"]:SetSize(horizontalEdgesNewSize, 1) - self.BorderEdgeTextures["Bottom"]:SetSize(horizontalEdgesNewSize, 1) + self.BorderEdgeTextures["Top"]:SetSize(horizontalEdgesNewSize + (self.options.horizontal_border_size_offset or 0), 1) + self.BorderEdgeTextures["Bottom"]:SetSize(horizontalEdgesNewSize + (self.options.horizontal_border_size_offset or 0), 1) end self.CenterBlock:Hide() @@ -613,6 +614,7 @@ function detailsFramework:AddRoundedCornersToFrame(frame, preset) --handle preset if (preset and type(preset) == "table") then + frame.options.horizontal_border_size_offset = preset.horizontal_border_size_offset applyPreset(frame, preset) else applyPreset(frame, defaultPreset) diff --git a/libs/DF/fw.lua b/libs/DF/fw.lua index 5f050f0a..80a6302e 100644 --- a/libs/DF/fw.lua +++ b/libs/DF/fw.lua @@ -1,6 +1,6 @@ -local dversion = 468 +local dversion = 482 local major, minor = "DetailsFramework-1.0", dversion local DF, oldminor = LibStub:NewLibrary(major, minor) @@ -11,6 +11,8 @@ end _G["DetailsFramework"] = DF +---@cast DF detailsframework + DetailsFrameworkCanLoad = true local SharedMedia = LibStub:GetLibrary("LibSharedMedia-3.0") @@ -19,7 +21,7 @@ local type = type local unpack = unpack local upper = string.upper local string_match = string.match -local tinsert = _G.tinsert +local tinsert = table.insert local abs = _G.abs local tremove = _G.tremove @@ -47,6 +49,8 @@ function DF:MsgWarning(msg, ...) print("|cFFFFFFAA" .. (self.__name or "Details!Framework") .. "|r |cFFFFAA00[Warning]|r", msg, ...) end +DF.internalFunctions = DF.internalFunctions or {} + local PixelUtil = PixelUtil or DFPixelUtil if (not PixelUtil) then --check if is in classic, TBC, or WotLK wow, if it is, build a replacement for PixelUtil @@ -491,6 +495,62 @@ function DF.table.find(t, value) end end +---get a value from a table using a path, e.g. getfrompath(tbl, "a.b.c") is the same as tbl.a.b.c +---@param t table +---@param path string +---@return any +function DF.table.getfrompath(t, path) + if (path:match("%.") or path:match("%[")) then + local value + + for key in path:gmatch("[%w_]+") do + value = t[key] or t[tonumber(key)] + + --check if the value is nil, if it is, the key does not exists in the table + if (not value) then + return + end + + --update t for the next iteration + t = value + end + + return value + else + return t[path] or t[tonumber(path)] + end +end + +---set the value of a table using a path, e.g. setfrompath(tbl, "a.b.c", 10) is the same as tbl.a.b.c = 10 +---@param t table +---@param path string +---@param value any +---@return boolean? +function DF.table.setfrompath(t, path, value) + if (path:match("%.") or path:match("%[")) then + local lastTable + local lastKey + + for key in path:gmatch("[%w_]+") do + lastTable = t + lastKey = key + + --update t for the next iteration + t = t[key] or t[tonumber(key)] + end + + if (lastTable and lastKey) then + lastTable[lastKey] = value + return true + end + else + t[path] = value + return true + end + + return false +end + ---find the value inside the table, and it it's not found, add it ---@param t table ---@param index integer|any @@ -689,10 +749,10 @@ local function tableToString(t, resultString, deep, seenTables) resultString = resultString .. space .. "[\"" .. key .. "\"] = \"|cFFfff1c1" .. value .. "|r\",\n" elseif (valueType == "number") then - resultString = resultString .. space .. "[\"" .. key .. "\"] = |cFFffc1f4" .. value .. "|r,\n" + resultString = resultString .. space .. "[\"" .. key .. "\"] = |cFF94CEA8" .. value .. "|r,\n" elseif (valueType == "function") then - resultString = resultString .. space .. "[\"" .. key .. "\"] = function()end,\n" + resultString = resultString .. space .. "[\"" .. key .. "\"] = |cFFC586C0function|r,\n" elseif (valueType == "boolean") then resultString = resultString .. space .. "[\"" .. key .. "\"] = |cFF99d0ff" .. (value and "true" or "false") .. "|r,\n" @@ -707,7 +767,6 @@ local function tableToStringSafe(t) return tableToString(t, nil, 0, seenTables) end - ---get the contends of table 't' and return it as a string ---@param t table ---@param resultString string @@ -936,6 +995,16 @@ function DF:GroupIterator(callback, ...) end end +---receives an object and a percent amount, then calculate the return value by multiplying the min value of the object width or height by the percent received +---@param uiObject uiobject +---@param percent number +---@return number +function DF:GetSizeFromPercent(uiObject, percent) + local width, height = uiObject:GetSize() + local minValue = math.min(width, height) + return minValue * percent +end + ---get an integer an format it as string with the time format 16:45 ---@param self table ---@param value number @@ -1038,7 +1107,7 @@ end ---@param self table ---@param fontString fontstring ---@param degrees number -function DF:SetFontRotation(fontString, degrees) --deprecated, use fontString:SetRotation(degrees) +function DF:SetFontRotation(fontString, degrees) --deprecated, use fontString:SetRotation(degrees) | retail use fontString:SetRotation(math.rad(degrees)) if (type(degrees) == "number") then if (not fontString.__rotationAnimation) then fontString.__rotationAnimation = DF:CreateAnimationHub(fontString) @@ -1093,6 +1162,14 @@ function DF:AddClassColorToText(text, className) return text end +---returns the class icon texture coordinates and texture file path +---@param class string +---@return number, number, number, number, string +function DF:GetClassTCoordsAndTexture(class) + local l, r, t, b = unpack(CLASS_ICON_TCOORDS[class]) + return l, r, t, b, [[Interface\WORLDSTATEFRAME\Icons-Classes]] +end + ---create a string with the spell icon and the spell name using |T|t scape codes to add the icon inside the string ---@param self table ---@param spellId any @@ -1105,14 +1182,6 @@ function DF:MakeStringFromSpellId(spellId) return "" end ----returns the class icon texture coordinates and texture file path ----@param class string ----@return number, number, number, number, string -function DF:GetClassTCoordsAndTexture(class) - local l, r, t, b = unpack(CLASS_ICON_TCOORDS[class]) - return l, r, t, b, [[Interface\WORLDSTATEFRAME\Icons-Classes]] -end - ---wrap 'text' with the class icon of 'playerName' using |T|t scape codes ---@param text string ---@param playerName string @@ -1159,6 +1228,58 @@ function DF:AddClassIconToText(text, playerName, englishClassName, useSpec, icon return text end +---create a table with information about a texture +---@param texture any +---@param textureWidth any +---@param textureHeight any +---@param imageWidth any +---@param imageHeight any +---@param left any +---@param right any +---@param top any +---@param bottom any +---@return table +function DF:CreateTextureInfo(texture, textureWidth, textureHeight, left, right, top, bottom, imageWidth, imageHeight) + local textureInfo = { + texture = texture, + width = textureWidth or 16, + height = textureHeight or 16, + coords = {left or 0, right or 1, top or 0, bottom or 1}, + } + + textureInfo.imageWidth = imageWidth or textureInfo.width + textureInfo.imageHeight = imageHeight or textureInfo.height + + return textureInfo +end + +---add a texture to the start or end of a string +---@param text string +---@param textureInfo table +---@param bAddSpace any +---@param bAddAfterText any +---@return string +function DF:AddTextureToText(text, textureInfo, bAddSpace, bAddAfterText) + local texture = textureInfo.texture + local textureWidth = textureInfo.width + local textureHeight = textureInfo.height + local imageWidth = textureInfo.imageWidth or textureWidth + local imageHeight = textureInfo.imageHeight or textureHeight + local left, right, top, bottom = unpack(textureInfo.coords) + left = left or 0 + right = right or 1 + top = top or 0 + bottom = bottom or 1 + + if (bAddAfterText) then + local newString = text .. (bAddSpace and " " or "") .. "|T" .. texture .. ":" .. textureWidth .. ":" .. textureHeight .. ":0:0:" .. imageWidth .. ":" .. imageHeight .. ":" .. (left * imageWidth) .. ":" .. (right * imageWidth) .. ":" .. (top * imageHeight) .. ":" .. (bottom * imageHeight) .. "|t" + return newString + else + local newString = "|T" .. texture .. ":" .. textureWidth .. ":" .. textureHeight .. ":0:0:" .. imageWidth .. ":" .. imageHeight .. ":" .. (left * imageWidth) .. ":" .. (right * imageWidth) .. ":" .. (top * imageHeight) .. ":" .. (bottom * imageHeight) .. "|t" .. (bAddSpace and " " or "") .. text + return newString + end +end + ---return the size of a fontstring ---@param fontString table ---@return number @@ -1182,6 +1303,15 @@ local ValidOutlines = { ["THICKOUTLINE"] = true, } +DF.FontOutlineFlags = { + {"NONE", "None"}, + {"MONOCHROME", "Monochrome"}, + {"OUTLINE", "Outline"}, + {"THICKOUTLINE", "Thick Outline"}, + {"OUTLINEMONOCHROME", "Outline & Monochrome"}, + {"THICKOUTLINEMONOCHROME", "Thick Outline & Monochrome"}, +} + ---set the outline of a fontstring, outline is a black border around the text, can be "NONE", "MONOCHROME", "OUTLINE" or "THICKOUTLINE" ---@param fontString table ---@param outline any @@ -1382,6 +1512,157 @@ function DF:GetSpellBookSpells() return spellNamesInSpellBook, spellIdsInSpellBook end +---return a table of passive talents, format: [spellId] = true +---@return {Name: string, ID: number, Texture: any, IsSelected: boolean}[] +function DF:GetAllTalents() + local allTalents = {} + + local configId = C_ClassTalents.GetActiveConfigID() + if (configId) then + local configInfo = C_Traits.GetConfigInfo(configId) + --get the spells from the SPEC from talents + for treeIndex, treeId in ipairs(configInfo.treeIDs) do + local treeNodes = C_Traits.GetTreeNodes(treeId) + for nodeIdIndex, treeNodeID in ipairs(treeNodes) do + local traitNodeInfo = C_Traits.GetNodeInfo(configId, treeNodeID) + if (traitNodeInfo) then + local activeEntry = traitNodeInfo.activeEntry + local entryIds = traitNodeInfo.entryIDs + for i = 1, #entryIds do + local entryId = entryIds[i] --number + local traitEntryInfo = C_Traits.GetEntryInfo(configId, entryId) + local borderTypes = Enum.TraitNodeEntryType + if (traitEntryInfo.type) then -- == borderTypes.SpendCircle + local definitionId = traitEntryInfo.definitionID + local traitDefinitionInfo = C_Traits.GetDefinitionInfo(definitionId) + local spellId = traitDefinitionInfo.overriddenSpellID or traitDefinitionInfo.spellID + local spellName, _, spellTexture = GetSpellInfo(spellId) + if (spellName) then + local talentInfo = {Name = spellName, ID = spellId, Texture = spellTexture, IsSelected = (activeEntry and activeEntry.rank and activeEntry.rank > 0) or false} + allTalents[#allTalents+1] = talentInfo + end + end + end + end + end + end + end + + return allTalents +end + +---return a table where keys are spellIds (number) and the value is true +---@return table +function DF:GetAvailableSpells() + local completeListOfSpells = {} + + --this line might not be compatible with classic + --local specId, specName, _, specIconTexture = GetSpecializationInfo(GetSpecialization()) + --local classNameLoc, className, classId = UnitClass("player") --not in use + local locPlayerRace, playerRace, playerRaceId = UnitRace("player") + + --get racials from the general tab + local generalTabIndex = 1 + local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(generalTabIndex) + offset = offset + 1 + local tabEnd = offset + numSpells + for entryOffset = offset, tabEnd - 1 do + local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player") + local spellData = LIB_OPEN_RAID_COOLDOWNS_INFO[spellId] + if (spellData) then + local raceId = spellData.raceid + if (raceId) then + if (type(raceId) == "table") then + if (raceId[playerRaceId]) then + spellId = C_SpellBook.GetOverrideSpell(spellId) + local spellName = GetSpellInfo(spellId) + local bIsPassive = IsPassiveSpell(spellId, "player") + if (spellName and not bIsPassive) then + completeListOfSpells[spellId] = true + end + end + + elseif (type(raceId) == "number") then + if (raceId == playerRaceId) then + spellId = C_SpellBook.GetOverrideSpell(spellId) + local spellName = GetSpellInfo(spellId) + local bIsPassive = IsPassiveSpell(spellId, "player") + if (spellName and not bIsPassive) then + completeListOfSpells[spellId] = true + end + end + end + end + end + end + + --get spells from the Spec spellbook + local amountOfTabs = GetNumSpellTabs() + for i = 2, amountOfTabs-1 do --starting at index 2 to ignore the general tab + local tabName, tabTexture, offset, numSpells, isGuild, offSpecId, shouldHide, specID = GetSpellTabInfo(i) + local bIsOffSpec = offSpecId ~= 0 + offset = offset + 1 + local tabEnd = offset + numSpells + for entryOffset = offset, tabEnd - 1 do + local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player") + if (spellId) then + if (spellType == "SPELL") then + spellId = C_SpellBook.GetOverrideSpell(spellId) + local spellName = GetSpellInfo(spellId) + local bIsPassive = IsPassiveSpell(spellId, "player") + if (spellName and not bIsPassive) then + completeListOfSpells[spellId] = bIsOffSpec == false + end + end + end + end + end + + --get class shared spells from the spell book + --[=[ + local tabName, tabTexture, offset, numSpells, isGuild, offSpecId = GetSpellTabInfo(2) + local bIsOffSpec = offSpecId ~= 0 + offset = offset + 1 + local tabEnd = offset + numSpells + for entryOffset = offset, tabEnd - 1 do + local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player") + if (spellId) then + if (spellType == "SPELL") then + spellId = C_SpellBook.GetOverrideSpell(spellId) + local spellName = GetSpellInfo(spellId) + local bIsPassive = IsPassiveSpell(spellId, "player") + + if (spellName and not bIsPassive) then + completeListOfSpells[spellId] = bIsOffSpec == false + end + end + end + end + --]=] + + local getNumPetSpells = function() + --'HasPetSpells' contradicts the name and return the amount of pet spells available instead of a boolean + return HasPetSpells() + end + + --get pet spells from the pet spellbook + local numPetSpells = getNumPetSpells() + if (numPetSpells) then + for i = 1, numPetSpells do + local spellName, _, unmaskedSpellId = GetSpellBookItemName(i, "pet") + if (unmaskedSpellId) then + unmaskedSpellId = C_SpellBook.GetOverrideSpell(unmaskedSpellId) + local bIsPassive = IsPassiveSpell(unmaskedSpellId, "pet") + if (spellName and not bIsPassive) then + completeListOfSpells[unmaskedSpellId] = true + end + end + end + end + + return completeListOfSpells +end + ------------------------------------------------------------------------------------------------------------------------ --flash animation @@ -1506,6 +1787,26 @@ end ---@field x number ---@field y number +DF.AnchorPoints = { + "Top Left", + "Left", + "Bottom Left", + "Bottom", + "Bottom Right", + "Right", + "Top Right", + "Top", + "Center", + "Inside Left", + "Inside Right", + "Inside Top", + "Inside Bottom", + "Inside Top Left", + "Inside Bottom Left", + "Inside Bottom Right", + "Inside Top Right", +} + local anchoringFunctions = { function(frame, anchorTo, offSetX, offSetY) --1 TOP LEFT frame:ClearAllPoints() @@ -1598,1343 +1899,176 @@ local anchoringFunctions = { ---@param anchorTable df_anchor ---@param anchorTo uiobject function DF:SetAnchor(widget, anchorTable, anchorTo) - anchorTo = anchorTo or widget:GetParent() - anchoringFunctions[anchorTable.side](widget, anchorTo, anchorTable.x, anchorTable.y) -end - ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---colors - - --add a new color name, the color can be query using DetailsFramework:ParseColors(colorName) - function DF:NewColor(colorName, red, green, blue, alpha) - assert(type(colorName) == "string", "DetailsFramework:NewColor(): colorName must be a string.") - assert(not DF.alias_text_colors[colorName], "DetailsFramework:NewColor(): colorName already exists.") - - red, green, blue, alpha = DetailsFramework:ParseColors(red, green, blue, alpha) - local colorTable = DetailsFramework:FormatColor("table", red, green, blue, alpha) - - DF.alias_text_colors[colorName] = colorTable - - return colorTable - end - - local colorTableMixin = { - GetColor = function(self) - return self.r, self.g, self.b, self.a - end, - - SetColor = function(self, r, g, b, a) - r, g, b, a = DF:ParseColors(r, g, b, a) - self.r = r or self.r - self.g = g or self.g - self.b = b or self.b - self.a = a or self.a - end, - - IsColorTable = true, - } - - ---convert a any format of color to any other format of color - ---@param newFormat string - ---@param r number|string - ---@param g number|nil - ---@param b number|nil - ---@param a number|nil - ---@param decimalsAmount number|nil - ---@return string|table|number|nil - ---@return number|nil - ---@return number|nil - ---@return number|nil - function DF:FormatColor(newFormat, r, g, b, a, decimalsAmount) - a = a or 1 - r, g, b, a = DF:ParseColors(r, g, b, a) - decimalsAmount = decimalsAmount or 4 - - r = DF:TruncateNumber(r, decimalsAmount) - g = DF:TruncateNumber(g, decimalsAmount) - b = DF:TruncateNumber(b, decimalsAmount) - a = DF:TruncateNumber(a, decimalsAmount) - - if (newFormat == "commastring") then - return r .. ", " .. g .. ", " .. b .. ", " .. a - - elseif (newFormat == "tablestring") then - return "{" .. r .. ", " .. g .. ", " .. b .. ", " .. a .. "}" - - elseif (newFormat == "table") then - return {r, g, b, a} - - elseif (newFormat == "tablemembers") then - return {["r"] = r, ["g"] = g, ["b"] = b, ["a"] = a} - - elseif (newFormat == "numbers") then - return r, g, b, a - - elseif (newFormat == "hex") then - return format("%.2x%.2x%.2x%.2x", a * 255, r * 255, g * 255, b * 255) - end - end - - function DF:CreateColorTable(r, g, b, a) - local t = { - r = r or 1, - g = g or 1, - b = b or 1, - a = a or 1, - } - DF:Mixin(t, colorTableMixin) - return t - end - - ---return true if DF.alias_text_colors has the colorName as a key - ---DF.alias_text_colors is a table where key is a color name and value is an indexed table with the r g b values - ---@param colorName any - ---@return unknown - function DF:IsHtmlColor(colorName) - return DF.alias_text_colors[colorName] - end - - ---get the values passed and return r g b a color values - ---the function accept color name, tables with r g b a members, indexed tables with r g b a values, numbers, html hex color - ---@param red any - ---@param green any - ---@param blue any - ---@param alpha any - ---@return number - ---@return number - ---@return number - ---@return number - function DF:ParseColors(red, green, blue, alpha) - local firstParameter = red - - --the first value passed is a table? - if (type(firstParameter) == "table") then - local colorTable = red - - if (colorTable.IsColorTable) then - --using colorTable mixin - return colorTable:GetColor() - - elseif (not colorTable[1] and colorTable.r) then - --{["r"] = 1, ["g"] = 1, ["b"] = 1} - red, green, blue, alpha = colorTable.r, colorTable.g, colorTable.b, colorTable.a - - else - --{1, .7, .2, 1} - red, green, blue, alpha = unpack(colorTable) - end - - --the first value passed is a string? - elseif (type(firstParameter) == "string") then - local colorString = red - --hexadecimal - if (string.find(colorString, "#")) then - colorString = colorString:gsub("#","") - if (string.len(colorString) == 8) then --with alpha - red, green, blue, alpha = tonumber("0x" .. colorString:sub(3, 4))/255, tonumber("0x" .. colorString:sub(5, 6))/255, tonumber("0x" .. colorString:sub(7, 8))/255, tonumber("0x" .. colorString:sub(1, 2))/255 - else - red, green, blue, alpha = tonumber("0x" .. colorString:sub(1, 2))/255, tonumber("0x" .. colorString:sub(3, 4))/255, tonumber("0x" .. colorString:sub(5, 6))/255, 1 - end - else - --name of the color - local colorTable = DF.alias_text_colors[colorString] - if (colorTable) then - red, green, blue, alpha = unpack(colorTable) - - --string with number separated by comma - elseif (colorString:find(",")) then - local r, g, b, a = strsplit(",", colorString) - red, green, blue, alpha = tonumber(r), tonumber(g), tonumber(b), tonumber(a) - - else - --no color found within the string, return default color - red, green, blue, alpha = unpack(DF.alias_text_colors.none) - end - end - end - - if (not red or type(red) ~= "number") then - red = 1 - end - if (not green) or type(green) ~= "number" then - green = 1 - end - if (not blue or type(blue) ~= "number") then - blue = 1 - end - if (not alpha or type(alpha) ~= "number") then - alpha = 1 - end - - --saturate the values before returning to make sure they are on the 0 to 1 range - return Saturate(red), Saturate(green), Saturate(blue), Saturate(alpha) - end - ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---menus - local formatOptionNameWithColon = function(text, useColon) - if (text) then - if (useColon) then - text = text .. ":" - return text - else - return text - end - end - end - - local widgetsToDisableOnCombat = {} - - local getMenuWidgetVolative = function(parent, widgetType, indexTable) - local widgetObject - - if (widgetType == "label") then - widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] - if (not widgetObject) then - widgetObject = DF:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType], "overlay") - tinsert(parent.widget_list, widgetObject) - tinsert(parent.widget_list_by_type[widgetType], widgetObject) - end - indexTable[widgetType] = indexTable[widgetType] + 1 - - elseif (widgetType == "dropdown") then - widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] - if (not widgetObject) then - widgetObject = DF:CreateDropDown(parent, function() return {} end, nil, 140, 18, nil, "$parentWidget" .. widgetType .. indexTable[widgetType]) - widgetObject.hasLabel = DF:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") - tinsert(parent.widget_list, widgetObject) - tinsert(parent.widget_list_by_type[widgetType], widgetObject) - - else - widgetObject:ClearHooks() - widgetObject.hasLabel.text = "" - end - indexTable[widgetType] = indexTable[widgetType] + 1 - - elseif (widgetType == "switch") then - widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] - if (not widgetObject) then - widgetObject = DF:CreateSwitch(parent, nil, true, 20, 20, nil, nil, nil, "$parentWidget" .. widgetType .. indexTable[widgetType]) - widgetObject.hasLabel = DF:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") - - tinsert(parent.widget_list, widgetObject) - tinsert(parent.widget_list_by_type[widgetType], widgetObject) - else - widgetObject:ClearHooks() - end - indexTable[widgetType] = indexTable[widgetType] + 1 - - elseif (widgetType == "slider") then - widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] - if (not widgetObject) then - widgetObject = DF:CreateSlider(parent, 140, 20, 1, 2, 1, 1, false, nil, "$parentWidget" .. widgetType .. indexTable[widgetType]) - widgetObject.hasLabel = DF:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") - - tinsert(parent.widget_list, widgetObject) - tinsert(parent.widget_list_by_type[widgetType], widgetObject) - else - widgetObject:ClearHooks() - end - indexTable[widgetType] = indexTable[widgetType] + 1 - - elseif (widgetType == "color") then - widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] - if (not widgetObject) then - widgetObject = DF:CreateColorPickButton(parent, "$parentWidget" .. widgetType .. indexTable[widgetType], nil, function()end, 1) - widgetObject.hasLabel = DF:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") - - tinsert(parent.widget_list, widgetObject) - tinsert(parent.widget_list_by_type[widgetType], widgetObject) - else - widgetObject:ClearHooks() - end - indexTable[widgetType] = indexTable[widgetType] + 1 - - elseif (widgetType == "button") then - widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] - if (not widgetObject) then - widgetObject = DF:CreateButton(parent, function()end, 120, 18, "", nil, nil, nil, nil, "$parentWidget" .. widgetType .. indexTable[widgetType]) - widgetObject.hasLabel = DF:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") - - tinsert(parent.widget_list, widgetObject) - tinsert(parent.widget_list_by_type[widgetType], widgetObject) - else - widgetObject:ClearHooks() - end - indexTable[widgetType] = indexTable[widgetType] + 1 - - elseif (widgetType == "textentry") then - widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]] - if (not widgetObject) then - widgetObject = DF:CreateTextEntry(parent, function()end, 120, 18, nil, "$parentWidget" .. widgetType .. indexTable[widgetType]) - widgetObject.hasLabel = DF:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay") - - tinsert(parent.widget_list, widgetObject) - tinsert(parent.widget_list_by_type[widgetType], widgetObject) - else - widgetObject:ClearHooks() - end - indexTable[widgetType] = indexTable[widgetType] + 1 - end - - --if the widget is inside the no combat table, remove it - for i = 1, #widgetsToDisableOnCombat do - if (widgetsToDisableOnCombat[i] == widgetObject) then - tremove(widgetsToDisableOnCombat, i) - break - end - end - - return widgetObject - end - - --get the description phrase from the language table or use the .desc or .deschraseid - local getDescPhraseText = function(languageTable, widgetTable) - local descPhraseId = languageTable and (languageTable[widgetTable.descPhraseId] or languageTable[widgetTable.desc]) - return descPhraseId or widgetTable.descPhraseId or widgetTable.desc or widgetTable.name or "-?-" - end - - local getNamePhraseText = function(languageTable, widgetTable, useColon) - local namePhrase = languageTable and (languageTable[widgetTable.namePhraseId] or languageTable[widgetTable.name]) - return namePhrase or formatOptionNameWithColon(widgetTable.name, useColon) or widgetTable.namePhraseId or widgetTable.name or "-?-" - end - - --volatile menu can be called several times, each time all settings are reset and a new menu is built using the same widgets - function DF:BuildMenuVolatile(parent, menuOptions, xOffset, yOffset, height, useColon, textTemplate, dropdownTemplate, switchTemplate, switchIsCheckbox, sliderTemplate, buttonTemplate, valueChangeHook) - if (not parent.widget_list) then - DF:SetAsOptionsPanel(parent) - end - DF:ClearOptionsPanel(parent) - - local currentXOffset = xOffset - local currentYOffset = yOffset - local maxColumnWidth = 0 - - local latestInlineWidget - - local widgetIndexes = { - label = 1, - dropdown = 1, - switch = 1, - slider = 1, - color = 1, - button = 1, - textentry = 1, - } - - height = abs((height or parent:GetHeight()) - abs(yOffset) + 20) - height = height * -1 - - --normalize format types - for index, widgetTable in ipairs(menuOptions) do - if (widgetTable.type == "space") then - widgetTable.type = "blank" - - elseif (widgetTable.type == "dropdown") then - widgetTable.type = "select" - - elseif (widgetTable.type == "switch") then - widgetTable.type = "toggle" - - elseif (widgetTable.type == "slider") then - widgetTable.type = "range" - - elseif (widgetTable.type == "button") then - widgetTable.type = "execute" - - end - end - - --catch some options added in the hash part of the menu table - local useBoxFirstOnAllWidgets = menuOptions.always_boxfirst - local languageAddonId = menuOptions.language_addonId - local languageTable - - if (languageAddonId) then - languageTable = DetailsFramework.Language.GetLanguageTable(languageAddonId) - end - - for index, widgetTable in ipairs(menuOptions) do - if (not widgetTable.hidden) then - - local widgetCreated - if (latestInlineWidget) then - if (not widgetTable.inline) then - latestInlineWidget = nil - currentYOffset = currentYOffset - 20 - end - end - - local extraPaddingY = 0 - - if (not widgetTable.novolatile) then - --step a line - if (widgetTable.type == "blank" or widgetTable.type == "space") then - --do nothing - - elseif (widgetTable.type == "label" or widgetTable.type == "text") then - local label = getMenuWidgetVolative(parent, "label", widgetIndexes) - widgetCreated = label - - local namePhrase = (languageTable and (languageTable[widgetTable.namePhraseId] or languageTable[widgetTable.name])) or (widgetTable.get and widgetTable.get()) or widgetTable.text or (widgetTable.namePhraseId) or "" - label.text = namePhrase - label.color = widgetTable.color - - if (widgetTable.font) then - label.fontface = widgetTable.font - end - - if (widgetTable.text_template or textTemplate) then - label:SetTemplate(widgetTable.text_template or textTemplate) - else - label.fontsize = widgetTable.size or 10 - end - - label._get = widgetTable.get - label.widget_type = "label" - label:ClearAllPoints() - label:SetPoint(currentXOffset, currentYOffset) - - if (widgetTable.id) then - parent.widgetids [widgetTable.id] = label - end - - --dropdowns - elseif (widgetTable.type == "select" or widgetTable.type == "dropdown") then - assert(widgetTable.get, "DetailsFramework:BuildMenu(): .get() not found in the widget table for 'select'") - local dropdown = getMenuWidgetVolative(parent, "dropdown", widgetIndexes) - widgetCreated = dropdown - - dropdown:SetFunction(widgetTable.values) - dropdown:Refresh() - dropdown:Select(widgetTable.get()) - dropdown:SetTemplate(dropdownTemplate) - - local descPhrase = getDescPhraseText(languageTable, widgetTable) - dropdown:SetTooltip(descPhrase) - dropdown._get = widgetTable.get - dropdown.widget_type = "select" - - local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon) - dropdown.hasLabel.text = namePhrase - - dropdown.hasLabel:SetTemplate(widgetTable.text_template or textTemplate) - dropdown:ClearAllPoints() - dropdown:SetPoint("left", dropdown.hasLabel, "right", 2) - dropdown.hasLabel:ClearAllPoints() - dropdown.hasLabel:SetPoint(currentXOffset, currentYOffset) - - --global callback - if (valueChangeHook) then - dropdown:SetHook("OnOptionSelected", valueChangeHook) - end - - --hook list (hook list is wiped when getting the widget) - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - dropdown:SetHook(hookName, hookFunc) - end - end - - if (widgetTable.id) then - parent.widgetids[widgetTable.id] = dropdown - end - - local widgetTotalSize = dropdown.hasLabel.widget:GetStringWidth() + 140 + 4 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end - - --switchs - elseif (widgetTable.type == "toggle" or widgetTable.type == "switch") then - local switch = getMenuWidgetVolative(parent, "switch", widgetIndexes) - widgetCreated = switch - - switch:SetValue(widgetTable.get()) - switch:SetTemplate(switchTemplate) - switch:SetAsCheckBox() --it's always a checkbox on volatile menu - - local descPhrase = getDescPhraseText(languageTable, widgetTable) - switch:SetTooltip(descPhrase) - switch._get = widgetTable.get - switch.widget_type = "toggle" - switch.OnSwitch = widgetTable.set - - if (valueChangeHook) then - switch:SetHook("OnSwitch", valueChangeHook) - end - - --hook list - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - switch:SetHook(hookName, hookFunc) - end - end - - if (widgetTable.width) then - switch:SetWidth(widgetTable.width) - end - if (widgetTable.height) then - switch:SetHeight(widgetTable.height) - end - - local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon) - switch.hasLabel.text = namePhrase - switch.hasLabel:SetTemplate(widgetTable.text_template or textTemplate) - - switch:ClearAllPoints() - switch.hasLabel:ClearAllPoints() - - if (widgetTable.boxfirst or useBoxFirstOnAllWidgets) then - switch:SetPoint(currentXOffset, currentYOffset) - switch.hasLabel:SetPoint("left", switch, "right", 2) - - local nextWidgetTable = menuOptions[index+1] - if (nextWidgetTable) then - if (nextWidgetTable.type ~= "blank" and nextWidgetTable.type ~= "breakline" and nextWidgetTable.type ~= "toggle" and nextWidgetTable.type ~= "color") then - extraPaddingY = 4 - end - end - else - switch.hasLabel:SetPoint(currentXOffset, currentYOffset) - switch:SetPoint("left", switch.hasLabel, "right", 2) - end - - if (widgetTable.id) then - parent.widgetids [widgetTable.id] = switch - end - - local widgetTotalSize = switch.hasLabel:GetStringWidth() + 32 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end - - --slider - elseif (widgetTable.type == "range" or widgetTable.type == "slider") then - local slider = getMenuWidgetVolative(parent, "slider", widgetIndexes) - widgetCreated = slider - - if (widgetTable.usedecimals) then - slider.slider:SetValueStep(0.01) - else - slider.slider:SetValueStep(widgetTable.step) - end - slider.useDecimals = widgetTable.usedecimals - - slider.slider:SetMinMaxValues(widgetTable.min, widgetTable.max) - slider.slider:SetValue(widgetTable.get()) - slider.ivalue = slider.slider:GetValue() - - slider:SetTemplate(sliderTemplate) - - local descPhrase = getDescPhraseText(languageTable, widgetTable) - slider:SetTooltip(descPhrase) - slider._get = widgetTable.get - slider.widget_type = "range" - slider:SetHook("OnValueChange", widgetTable.set) - - if (valueChangeHook) then - slider:SetHook("OnValueChange", valueChangeHook) - end - - if (widgetTable.thumbscale) then - slider:SetThumbSize (slider.thumb.originalWidth * widgetTable.thumbscale, nil) - else - slider:SetThumbSize (slider.thumb.originalWidth * 1.3, nil) - end - - --hook list - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - slider:SetHook(hookName, hookFunc) - end - end - - local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon) - slider.hasLabel.text = namePhrase - slider.hasLabel:SetTemplate(widgetTable.text_template or textTemplate) - - slider:SetPoint("left", slider.hasLabel, "right", 2) - slider.hasLabel:SetPoint(currentXOffset, currentYOffset) - - if (widgetTable.id) then - parent.widgetids[widgetTable.id] = slider - end - - local widgetTotalSize = slider.hasLabel:GetStringWidth() + 146 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end - - --color - elseif (widgetTable.type == "color" or widgetTable.type == "color") then - local colorpick = getMenuWidgetVolative(parent, "color", widgetIndexes) - widgetCreated = colorpick - - colorpick.color_callback = widgetTable.set --callback - colorpick:SetTemplate(buttonTemplate) - colorpick:SetSize(18, 18) - - local descPhrase = getDescPhraseText(languageTable, widgetTable) - colorpick:SetTooltip(descPhrase) - colorpick._get = widgetTable.get - colorpick.widget_type = "color" - - local default_value, g, b, a = widgetTable.get() - if (type(default_value) == "table") then - colorpick:SetColor(unpack(default_value)) - else - colorpick:SetColor(default_value, g, b, a) - end - - if (valueChangeHook) then - colorpick:SetHook("OnColorChanged", valueChangeHook) - end - - --hook list - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - colorpick:SetHook(hookName, hookFunc) - end - end - - local label = colorpick.hasLabel - - local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon) - label.text = namePhrase - label:SetTemplate(widgetTable.text_template or textTemplate) - - label:ClearAllPoints() - colorpick:ClearAllPoints() - - if (widgetTable.boxfirst or useBoxFirstOnAllWidgets) then - label:SetPoint("left", colorpick, "right", 2) - colorpick:SetPoint(currentXOffset, currentYOffset) - extraPaddingY = 1 - else - colorpick:SetPoint("left", label, "right", 2) - label:SetPoint(currentXOffset, currentYOffset) - end - - if (widgetTable.id) then - parent.widgetids[widgetTable.id] = colorpick - end - - local widgetTotalSize = label:GetStringWidth() + 32 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end - - --button - elseif (widgetTable.type == "execute" or widgetTable.type == "button") then - local button = getMenuWidgetVolative(parent, "button", widgetIndexes) - widgetCreated = button - - button:SetTemplate(buttonTemplate) - button:SetSize(widgetTable.width or 120, widgetTable.height or 18) - button:SetClickFunction(widgetTable.func, widgetTable.param1, widgetTable.param2) - - local textTemplate = widgetTable.text_template or textTemplate or DF.font_templates["ORANGE_FONT_TEMPLATE"] - button.textcolor = textTemplate.color - button.textfont = textTemplate.font - button.textsize = textTemplate.size - - local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon) - button.text = namePhrase - - if (widgetTable.inline) then - if (latestInlineWidget) then - button:SetPoint("left", latestInlineWidget, "right", 2, 0) - latestInlineWidget = button - else - button:SetPoint(currentXOffset, currentYOffset) - latestInlineWidget = button - end - else - button:SetPoint(currentXOffset, currentYOffset) - end - - local descPhrase = getDescPhraseText(languageTable, widgetTable) - button:SetTooltip(descPhrase) - button.widget_type = "execute" - - --hook list - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - button:SetHook(hookName, hookFunc) - end - end - - if (widgetTable.width) then - button:SetWidth(widgetTable.width) - end - if (widgetTable.height) then - button:SetHeight(widgetTable.height) - end - - if (widgetTable.id) then - parent.widgetids[widgetTable.id] = button - end - - local widgetTotalSize = button:GetWidth() + 4 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end - - --textentry - elseif (widgetTable.type == "textentry") then - local textentry = getMenuWidgetVolative(parent, "textentry", widgetIndexes) - widgetCreated = textentry - - textentry:SetCommitFunction(widgetTable.func or widgetTable.set) - textentry:SetTemplate(widgetTable.template or widgetTable.button_template or buttonTemplate) - textentry:SetSize(widgetTable.width or 120, widgetTable.height or 18) - - local descPhrase = getDescPhraseText(languageTable, widgetTable) - textentry:SetTooltip(descPhrase) - textentry.text = widgetTable.get() - textentry._get = widgetTable.get - textentry.widget_type = "textentry" - textentry:SetHook("OnEnterPressed", function(...) - local upFunc = widgetTable.func or widgetTable.set - upFunc(...) - if (valueChangeHook) then - valueChangeHook() - end - end) - textentry:SetHook("OnEditFocusLost", function(...) - local upFunc = widgetTable.func or widgetTable.set - upFunc(...) - if (valueChangeHook) then - valueChangeHook() - end - end) - - local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon) - textentry.hasLabel.text = namePhrase - textentry.hasLabel:SetTemplate(widgetTable.text_template or textTemplate) - textentry:SetPoint("left", textentry.hasLabel, "right", 2) - textentry.hasLabel:SetPoint(currentXOffset, currentYOffset) - - --hook list - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - textentry:SetHook(hookName, hookFunc) - end - end - - if (widgetTable.id) then - parent.widgetids[widgetTable.id] = textentry - end - - local widgetTotalSize = textentry.hasLabel:GetStringWidth() + 64 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end - - end --end loop - - if (widgetTable.nocombat) then - tinsert(widgetsToDisableOnCombat, widgetCreated) - end - - if (not widgetTable.inline) then - if (widgetTable.spacement) then - currentYOffset = currentYOffset - 30 - else - currentYOffset = currentYOffset - 20 - end - end - - if (extraPaddingY > 0) then - currentYOffset = currentYOffset - extraPaddingY - end - - if (widgetTable.type == "breakline" or currentYOffset < height) then - currentYOffset = yOffset - currentXOffset = currentXOffset + maxColumnWidth + 20 - maxColumnWidth = 0 - end - - if widgetCreated then - widgetCreated:Show() - end - end - end - end - - DF.RefreshUnsafeOptionsWidgets() - end - - local getDescripttionPhraseID = function(widgetTable, languageAddonId, languageTable) - if (widgetTable.descPhraseId) then - return widgetTable.descPhraseId - end - - if (not languageTable) then - return - end - - local hasValue = DF.Language.DoesPhraseIDExistsInDefaultLanguage(languageAddonId, widgetTable.desc) - if (not hasValue) then - return - end - - return widgetTable.desc - end - - local getNamePhraseID = function(widgetTable, languageAddonId, languageTable) - if (widgetTable.namePhraseId) then - return widgetTable.namePhraseId - end - - if (not languageTable) then - return - end - - local keyName = widgetTable.name - - if (widgetTable.type == "label" and widgetTable.get) then - local key = widgetTable.get() - if (key and type(key) == "string") then - keyName = key - end - end - - --embed key is when the phraseId is inside a string surounded by @ - local embedPhraseId = keyName:match("@(.-)@") - - local hasValue = DF.Language.DoesPhraseIDExistsInDefaultLanguage(languageAddonId, embedPhraseId or keyName) - if (not hasValue) then - return - end - - return keyName - end - - function DF:BuildMenu(parent, menuOptions, xOffset, yOffset, height, useColon, textTemplate, dropdownTemplate, switchTemplate, switchIsCheckbox, sliderTemplate, buttonTemplate, valueChangeHook) - if (not parent.widget_list) then - DF:SetAsOptionsPanel(parent) - end - - local currentXOffset = xOffset - local currentYOffset = yOffset - local maxColumnWidth = 0 - - --how many widgets has been created on this line loop pass - local amountLineWidgetCreated = 0 - local latestInlineWidget - - height = abs((height or parent:GetHeight()) - abs(yOffset) + 20) - height = height * -1 - - --normalize format types - for index, widgetTable in ipairs(menuOptions) do - if (widgetTable.type == "space") then - widgetTable.type = "blank" - - elseif (widgetTable.type == "dropdown") then - widgetTable.type = "select" - - elseif (widgetTable.type == "switch") then - widgetTable.type = "toggle" - - elseif (widgetTable.type == "slider") then - widgetTable.type = "range" - - elseif (widgetTable.type == "button") then - widgetTable.type = "execute" - end - end - - --catch some options added in the hash part of the menu table - local useBoxFirstOnAllWidgets = menuOptions.always_boxfirst - local languageAddonId = menuOptions.language_addonId - local languageTable - - if (languageAddonId) then - languageTable = DetailsFramework.Language.GetLanguageTable(languageAddonId) - end - - for index, widgetTable in ipairs(menuOptions) do - if (not widgetTable.hidden) then - - local widgetCreated - if (latestInlineWidget) then - if (not widgetTable.inline) then - latestInlineWidget = nil - currentYOffset = currentYOffset - 28 - end - end - - local extraPaddingY = 0 - - if (widgetTable.type == "blank") then - --do nothing - - elseif (widgetTable.type == "label" or widgetTable.type == "text") then - local label = DF:CreateLabel(parent, "", widgetTable.text_template or textTemplate or widgetTable.size, widgetTable.color, widgetTable.font, nil, "$parentWidget" .. index, "overlay") - label._get = widgetTable.get - label.widget_type = "label" - label:SetPoint(currentXOffset, currentYOffset) - - local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable) - if (namePhraseId) then - DetailsFramework.Language.RegisterObject(languageAddonId, label.widget, namePhraseId) - label.languageAddonId = languageAddonId - else - local textToSet = (widgetTable.get and widgetTable.get()) or widgetTable.text or "" - label:SetText(textToSet) - end - - --store the widget created into the overall table and the widget by type - tinsert(parent.widget_list, label) - tinsert(parent.widget_list_by_type.label, label) - - amountLineWidgetCreated = amountLineWidgetCreated + 1 - - if (widgetTable.id) then - parent.widgetids[widgetTable.id] = label - end - - elseif (widgetTable.type == "select") then - assert(widgetTable.get, "DetailsFramework:BuildMenu(): .get not found in the widget table for 'select'") - local dropdown = DF:NewDropDown(parent, nil, "$parentWidget" .. index, nil, 140, 18, widgetTable.values, widgetTable.get(), dropdownTemplate) - - local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, dropdown, "have_tooltip", descPhraseId, widgetTable.desc) - - dropdown._get = widgetTable.get - dropdown.widget_type = "select" - - local label = DF:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12) - local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon)) - - dropdown.addonId = languageAddonId - if (languageAddonId) then - DF.Language.RegisterCallback(languageAddonId, function(addonId, languageId, ...) dropdown:Select(dropdown:GetValue()) end) - C_Timer.After(0.1, function() dropdown:Select(dropdown:GetValue()) end) - end - - dropdown:SetPoint("left", label, "right", 2) - label:SetPoint(currentXOffset, currentYOffset) - dropdown.hasLabel = label - - --global callback - if (valueChangeHook) then - dropdown:SetHook("OnOptionSelected", valueChangeHook) - end - - --hook list - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - dropdown:SetHook(hookName, hookFunc) - end - end - - if (widgetTable.id) then - parent.widgetids[widgetTable.id] = dropdown - end - - local widgetTotalSize = label.widget:GetStringWidth() + 144 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end - - --store the widget created into the overall table and the widget by type - tinsert(parent.widget_list, dropdown) - tinsert(parent.widget_list_by_type.dropdown, dropdown) - - widgetCreated = dropdown - amountLineWidgetCreated = amountLineWidgetCreated + 1 - - elseif (widgetTable.type == "toggle") then - local switch = DF:NewSwitch(parent, nil, "$parentWidget" .. index, nil, 60, 20, nil, nil, widgetTable.get(), nil, nil, nil, nil, switchTemplate) - - local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, switch, "have_tooltip", descPhraseId, widgetTable.desc) - - switch._get = widgetTable.get - switch.widget_type = "toggle" - switch.OnSwitch = widgetTable.set - - if (switchIsCheckbox) then - switch:SetAsCheckBox() - end - - if (valueChangeHook) then - switch:SetHook("OnSwitch", valueChangeHook) - end - - --hook list - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - switch:SetHook(hookName, hookFunc) - end - end - - if (widgetTable.width) then - switch:SetWidth(widgetTable.width) - end - if (widgetTable.height) then - switch:SetHeight(widgetTable.height) - end - - local label = DF:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12) - - local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon)) - - if (widgetTable.boxfirst or useBoxFirstOnAllWidgets) then - switch:SetPoint(currentXOffset, currentYOffset) - label:SetPoint("left", switch, "right", 2) - - local nextWidgetTable = menuOptions[index+1] - if (nextWidgetTable) then - if (nextWidgetTable.type ~= "blank" and nextWidgetTable.type ~= "breakline" and nextWidgetTable.type ~= "toggle" and nextWidgetTable.type ~= "color") then - extraPaddingY = 4 - end - end - else - label:SetPoint(currentXOffset, currentYOffset) - switch:SetPoint("left", label, "right", 2, 0) - end - switch.hasLabel = label - - if (widgetTable.id) then - parent.widgetids[widgetTable.id] = switch - end - - local widgetTotalSize = label.widget:GetStringWidth() + 32 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end - - --store the widget created into the overall table and the widget by type - tinsert(parent.widget_list, switch) - tinsert(parent.widget_list_by_type.switch, switch) - - widgetCreated = switch - amountLineWidgetCreated = amountLineWidgetCreated + 1 - - elseif (widgetTable.type == "range") then - assert(widgetTable.get, "DetailsFramework:BuildMenu(): .get not found in the widget table for 'range'") - local isDecimanls = widgetTable.usedecimals - local slider = DF:NewSlider(parent, nil, "$parentWidget" .. index, nil, 140, 20, widgetTable.min, widgetTable.max, widgetTable.step, widgetTable.get(), isDecimanls, nil, nil, sliderTemplate) - - local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, slider, "have_tooltip", descPhraseId, widgetTable.desc) - - slider._get = widgetTable.get - slider.widget_type = "range" - slider:SetHook("OnValueChange", widgetTable.set) - - if (widgetTable.thumbscale) then - slider:SetThumbSize(slider.thumb:GetWidth() * widgetTable.thumbscale, nil) - else - slider:SetThumbSize(slider.thumb:GetWidth() * 1.3, nil) - end - - if (valueChangeHook) then - slider:SetHook("OnValueChange", valueChangeHook) - end - - --hook list - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - slider:SetHook(hookName, hookFunc) - end - end - - local label = DF:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12) - local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon)) - - slider:SetPoint("left", label, "right", 2) - label:SetPoint(currentXOffset, currentYOffset) - slider.hasLabel = label - - if (widgetTable.id) then - parent.widgetids[widgetTable.id] = slider - end - - local widgetTotalSize = label.widget:GetStringWidth() + 146 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end - - --store the widget created into the overall table and the widget by type - tinsert(parent.widget_list, slider) - tinsert(parent.widget_list_by_type.slider, slider) - - widgetCreated = slider - amountLineWidgetCreated = amountLineWidgetCreated + 1 - - elseif (widgetTable.type == "color") then - assert(widgetTable.get, "DetailsFramework:BuildMenu(): .get not found in the widget table for 'color'") - local colorpick = DF:NewColorPickButton(parent, "$parentWidget" .. index, nil, widgetTable.set, nil, buttonTemplate) - - local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, colorpick, "have_tooltip", descPhraseId, widgetTable.desc) - - colorpick._get = widgetTable.get - colorpick.widget_type = "color" - colorpick:SetSize(18, 18) - - local r, g, b, a = DF:ParseColors(widgetTable.get()) - colorpick:SetColor(r, g, b, a) - - if (valueChangeHook) then - colorpick:SetHook("OnColorChanged", valueChangeHook) - end - - --hook list - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - colorpick:SetHook(hookName, hookFunc) - end - end - - local label = DF:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12) - local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon)) - - if (widgetTable.boxfirst or useBoxFirstOnAllWidgets) then - label:SetPoint("left", colorpick, "right", 2) - colorpick:SetPoint(currentXOffset, currentYOffset) - extraPaddingY = 1 - else - colorpick:SetPoint("left", label, "right", 2) - label:SetPoint(currentXOffset, currentYOffset) - end - - colorpick.hasLabel = label - - if (widgetTable.id) then - parent.widgetids[widgetTable.id] = colorpick - end - - local widgetTotalSize = label.widget:GetStringWidth() + 32 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end - - --store the widget created into the overall table and the widget by type - tinsert(parent.widget_list, colorpick) - tinsert(parent.widget_list_by_type.color, colorpick) - - widgetCreated = colorpick - amountLineWidgetCreated = amountLineWidgetCreated + 1 - - elseif (widgetTable.type == "execute") then - local button = DF:NewButton(parent, nil, "$parentWidget" .. index, nil, 120, 18, widgetTable.func, widgetTable.param1, widgetTable.param2, nil, "", nil, buttonTemplate, textTemplate) - - local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, button.widget, namePhraseId, widgetTable.name) + anchorTo = anchorTo or widget:GetParent() + anchoringFunctions[anchorTable.side](widget, anchorTo, anchorTable.x, anchorTable.y) +end - if (not buttonTemplate) then - button:InstallCustomTexture() - end +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--colors - if (widgetTable.inline) then - if (latestInlineWidget) then - button:SetPoint("left", latestInlineWidget, "right", 2, 0) - latestInlineWidget = button - else - button:SetPoint(currentXOffset, currentYOffset) - latestInlineWidget = button - end - else - button:SetPoint(currentXOffset, currentYOffset) - end + --add a new color name, the color can be query using DetailsFramework:ParseColors(colorName) + function DF:NewColor(colorName, red, green, blue, alpha) + assert(type(colorName) == "string", "DetailsFramework:NewColor(): colorName must be a string.") + --assert(not DF.alias_text_colors[colorName], "DetailsFramework:NewColor(): colorName already exists.") - local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, button, "have_tooltip", descPhraseId, widgetTable.desc) + red, green, blue, alpha = DetailsFramework:ParseColors(red, green, blue, alpha) + local colorTable = DetailsFramework:FormatColor("table", red, green, blue, alpha) - button.widget_type = "execute" + DF.alias_text_colors[colorName] = colorTable - --button icon - if (widgetTable.icontexture) then - button:SetIcon(widgetTable.icontexture, nil, nil, nil, widgetTable.icontexcoords, nil, nil, 2) - end + return colorTable + end - --hook list - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - button:SetHook(hookName, hookFunc) - end - end + local colorTableMixin = { + GetColor = function(self) + return self.r, self.g, self.b, self.a + end, - if (widgetTable.id) then - parent.widgetids [widgetTable.id] = button - end + SetColor = function(self, r, g, b, a) + r, g, b, a = DF:ParseColors(r, g, b, a) + self.r = r or self.r + self.g = g or self.g + self.b = b or self.b + self.a = a or self.a + end, - if (widgetTable.width) then - button:SetWidth(widgetTable.width) - end - if (widgetTable.height) then - button:SetHeight(widgetTable.height) - end + IsColorTable = true, + } - local widgetTotalSize = button:GetWidth() + 4 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end + ---convert a any format of color to any other format of color + ---@param newFormat string + ---@param r number|string + ---@param g number|nil + ---@param b number|nil + ---@param a number|nil + ---@param decimalsAmount number|nil + ---@return string|table|number|nil + ---@return number|nil + ---@return number|nil + ---@return number|nil + function DF:FormatColor(newFormat, r, g, b, a, decimalsAmount) + a = a or 1 + r, g, b, a = DF:ParseColors(r, g, b, a) + decimalsAmount = decimalsAmount or 4 - --store the widget created into the overall table and the widget by type - tinsert(parent.widget_list, button) - tinsert(parent.widget_list_by_type.button, button) + r = DF:TruncateNumber(r, decimalsAmount) + g = DF:TruncateNumber(g, decimalsAmount) + b = DF:TruncateNumber(b, decimalsAmount) + a = DF:TruncateNumber(a, decimalsAmount) - widgetCreated = button - amountLineWidgetCreated = amountLineWidgetCreated + 1 + if (newFormat == "commastring") then + return r .. ", " .. g .. ", " .. b .. ", " .. a - elseif (widgetTable.type == "textentry") then - local textentry = DF:CreateTextEntry(parent, widgetTable.func or widgetTable.set, 120, 18, nil, "$parentWidget" .. index, nil, buttonTemplate) - textentry.align = widgetTable.align or "left" + elseif (newFormat == "tablestring") then + return "{" .. r .. ", " .. g .. ", " .. b .. ", " .. a .. "}" - local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, textentry, "have_tooltip", descPhraseId, widgetTable.desc) + elseif (newFormat == "table") then + return {r, g, b, a} - textentry.text = widgetTable.get() - textentry._get = widgetTable.get - textentry.widget_type = "textentry" - textentry:SetHook("OnEnterPressed", widgetTable.func or widgetTable.set) - textentry:SetHook("OnEditFocusLost", widgetTable.func or widgetTable.set) + elseif (newFormat == "tablemembers") then + return {["r"] = r, ["g"] = g, ["b"] = b, ["a"] = a} - local label = DF:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12) + elseif (newFormat == "numbers") then + return r, g, b, a - local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable) - DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon)) + elseif (newFormat == "hex") then + return format("%.2x%.2x%.2x%.2x", a * 255, r * 255, g * 255, b * 255) + end + end - textentry:SetPoint("left", label, "right", 2) - label:SetPoint(currentXOffset, currentYOffset) - textentry.hasLabel = label + function DF:CreateColorTable(r, g, b, a) + local t = { + r = r or 1, + g = g or 1, + b = b or 1, + a = a or 1, + } + DF:Mixin(t, colorTableMixin) + return t + end - --hook list - if (widgetTable.hooks) then - for hookName, hookFunc in pairs(widgetTable.hooks) do - textentry:SetHook(hookName, hookFunc) - end - end + ---return true if DF.alias_text_colors has the colorName as a key + ---DF.alias_text_colors is a table where key is a color name and value is an indexed table with the r g b values + ---@param colorName any + ---@return unknown + function DF:IsHtmlColor(colorName) + return DF.alias_text_colors[colorName] + end - if (widgetTable.id) then - parent.widgetids [widgetTable.id] = textentry - end + ---get the values passed and return r g b a color values + ---the function accept color name, tables with r g b a members, indexed tables with r g b a values, numbers, html hex color + ---@param red any + ---@param green any + ---@param blue any + ---@param alpha any + ---@return number + ---@return number + ---@return number + ---@return number + function DF:ParseColors(red, green, blue, alpha) + local firstParameter = red - local widgetTotalSize = label.widget:GetStringWidth() + 64 - if (widgetTotalSize > maxColumnWidth) then - maxColumnWidth = widgetTotalSize - end + --the first value passed is a table? + if (type(firstParameter) == "table") then + local colorTable = red - --store the widget created into the overall table and the widget by type - tinsert(parent.widget_list, textentry) - tinsert(parent.widget_list_by_type.textentry, textentry) + if (colorTable.IsColorTable) then + --using colorTable mixin + return colorTable:GetColor() - widgetCreated = textentry - amountLineWidgetCreated = amountLineWidgetCreated + 1 - end + elseif (not colorTable[1] and colorTable.r) then + --{["r"] = 1, ["g"] = 1, ["b"] = 1} + red, green, blue, alpha = colorTable.r, colorTable.g, colorTable.b, colorTable.a - if (widgetTable.nocombat) then - tinsert(widgetsToDisableOnCombat, widgetCreated) - end + else + --{1, .7, .2, 1} + red, green, blue, alpha = unpack(colorTable) + end - if (not widgetTable.inline) then - if (widgetTable.spacement) then - currentYOffset = currentYOffset - 30 - else - currentYOffset = currentYOffset - 20 - end + --the first value passed is a string? + elseif (type(firstParameter) == "string") then + local colorString = red + --hexadecimal + if (string.find(colorString, "#")) then + colorString = colorString:gsub("#","") + if (string.len(colorString) == 8) then --with alpha + red, green, blue, alpha = tonumber("0x" .. colorString:sub(3, 4))/255, tonumber("0x" .. colorString:sub(5, 6))/255, tonumber("0x" .. colorString:sub(7, 8))/255, tonumber("0x" .. colorString:sub(1, 2))/255 + else + red, green, blue, alpha = tonumber("0x" .. colorString:sub(1, 2))/255, tonumber("0x" .. colorString:sub(3, 4))/255, tonumber("0x" .. colorString:sub(5, 6))/255, 1 end + else + --name of the color + local colorTable = DF.alias_text_colors[colorString] + if (colorTable) then + red, green, blue, alpha = unpack(colorTable) - if (extraPaddingY > 0) then - currentYOffset = currentYOffset - extraPaddingY - end + --string with number separated by comma + elseif (colorString:find(",")) then + local r, g, b, a = strsplit(",", colorString) + red, green, blue, alpha = tonumber(r), tonumber(g), tonumber(b), tonumber(a) - if (widgetTable.type == "breakline" or currentYOffset < height) then - currentYOffset = yOffset - currentXOffset = currentXOffset + maxColumnWidth + 20 - amountLineWidgetCreated = 0 - maxColumnWidth = 0 + else + --no color found within the string, return default color + red, green, blue, alpha = unpack(DF.alias_text_colors.none) end end end - DF.RefreshUnsafeOptionsWidgets() - end - - local lockNotSafeWidgetsForCombat = function() - for _, widget in ipairs(widgetsToDisableOnCombat) do - widget:Disable() - end - end - - local unlockNotSafeWidgetsForCombat = function() - for _, widget in ipairs(widgetsToDisableOnCombat) do - widget:Enable() + if (not red or type(red) ~= "number") then + red = 1 end - end - - function DF.RefreshUnsafeOptionsWidgets() - if (DF.PlayerHasCombatFlag) then - lockNotSafeWidgetsForCombat() - else - unlockNotSafeWidgetsForCombat() + if (not green) or type(green) ~= "number" then + green = 1 end - end - - DF.PlayerHasCombatFlag = false - local ProtectCombatFrame = CreateFrame("frame") - ProtectCombatFrame:RegisterEvent("PLAYER_REGEN_ENABLED") - ProtectCombatFrame:RegisterEvent("PLAYER_REGEN_DISABLED") - ProtectCombatFrame:RegisterEvent("PLAYER_ENTERING_WORLD") - ProtectCombatFrame:SetScript("OnEvent", function(self, event) - if (event == "PLAYER_ENTERING_WORLD") then - if (InCombatLockdown()) then - DF.PlayerHasCombatFlag = true - else - DF.PlayerHasCombatFlag = false - end - DF.RefreshUnsafeOptionsWidgets() - - elseif (event == "PLAYER_REGEN_ENABLED") then - DF.PlayerHasCombatFlag = false - DF.RefreshUnsafeOptionsWidgets() - - elseif (event == "PLAYER_REGEN_DISABLED") then - DF.PlayerHasCombatFlag = true - DF.RefreshUnsafeOptionsWidgets() + if (not blue or type(blue) ~= "number") then + blue = 1 end - end) - - function DF:CreateInCombatTexture(frame) - if (DF.debug and not frame) then - error("Details! Framework: CreateInCombatTexture invalid frame on parameter 1.") + if (not alpha or type(alpha) ~= "number") then + alpha = 1 end - local inCombatBackgroundTexture = DF:CreateImage(frame) - inCombatBackgroundTexture:SetColorTexture(.6, 0, 0, .1) - inCombatBackgroundTexture:Hide() - - local inCombatLabel = Plater:CreateLabel(frame, "you are in combat", 24, "silver") - inCombatLabel:SetPoint("right", inCombatBackgroundTexture, "right", -10, 0) - inCombatLabel:Hide() - - frame:RegisterEvent("PLAYER_REGEN_DISABLED") - frame:RegisterEvent("PLAYER_REGEN_ENABLED") - - frame:SetScript("OnEvent", function(self, event) - if (event == "PLAYER_REGEN_DISABLED") then - inCombatBackgroundTexture:Show() - inCombatLabel:Show() - - elseif (event == "PLAYER_REGEN_ENABLED") then - inCombatBackgroundTexture:Hide() - inCombatLabel:Hide() - end - end) - - return inCombatBackgroundTexture + --saturate the values before returning to make sure they are on the 0 to 1 range + return Saturate(red), Saturate(green), Saturate(blue), Saturate(alpha) end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2968,67 +2102,6 @@ end TutorialAlertFrame:Show() end - local refresh_options = function(self) - for _, widget in ipairs(self.widget_list) do - if (widget._get) then - if (widget.widget_type == "label") then - if (widget._get() and not widget.languageAddonId) then - widget:SetText(widget._get()) - end - - elseif (widget.widget_type == "select") then - widget:Select(widget._get()) - - elseif (widget.widget_type == "toggle" or widget.widget_type == "range") then - widget:SetValue(widget._get()) - - elseif (widget.widget_type == "textentry") then - widget:SetText(widget._get()) - - elseif (widget.widget_type == "color") then - local default_value, g, b, a = widget._get() - if (type(default_value) == "table") then - widget:SetColor (unpack(default_value)) - - else - widget:SetColor (default_value, g, b, a) - end - end - end - end - end - - local get_frame_by_id = function(self, id) - return self.widgetids [id] - end - - function DF:ClearOptionsPanel(frame) - for i = 1, #frame.widget_list do - frame.widget_list[i]:Hide() - if (frame.widget_list[i].hasLabel) then - frame.widget_list[i].hasLabel:SetText("") - end - end - - table.wipe(frame.widgetids) - end - - function DF:SetAsOptionsPanel(frame) - frame.RefreshOptions = refresh_options - frame.widget_list = {} - frame.widget_list_by_type = { - ["dropdown"] = {}, -- "select" - ["switch"] = {}, -- "toggle" - ["slider"] = {}, -- "range" - ["color"] = {}, -- - ["button"] = {}, -- "execute" - ["textentry"] = {}, -- - ["label"] = {}, --"text" - } - frame.widgetids = {} - frame.GetWidgetById = get_frame_by_id - end - function DF:CreateOptionsFrame(name, title, template) template = template or 1 @@ -3037,7 +2110,7 @@ end tinsert(UISpecialFrames, name) newOptionsFrame:SetSize(500, 200) - newOptionsFrame.RefreshOptions = refresh_options + newOptionsFrame.RefreshOptions = DF.internalFunctions.RefreshOptionsPanel newOptionsFrame.widget_list = {} newOptionsFrame:SetScript("OnMouseDown", function(self, button) @@ -3075,7 +2148,7 @@ end tinsert(UISpecialFrames, name) newOptionsFrame:SetSize(500, 200) - newOptionsFrame.RefreshOptions = refresh_options + newOptionsFrame.RefreshOptions = DF.internalFunctions.RefreshOptionsPanel newOptionsFrame.widget_list = {} newOptionsFrame:SetScript("OnMouseDown", function(self, button) @@ -3311,6 +2384,12 @@ DF.button_templates["OPTIONS_BUTTON_TEMPLATE"] = { backdropbordercolor = {0, 0, 0, 1}, } +DF.button_templates["OPTIONS_BUTTON_GOLDENBORDER_TEMPLATE"] = { + backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, + backdropcolor = {1, 1, 1, .5}, + backdropbordercolor = {1, 0.785, 0, 1}, +} + --sliders DF.slider_templates = DF.slider_templates or {} DF.slider_templates["OPTIONS_SLIDER_TEMPLATE"] = { @@ -3325,6 +2404,12 @@ DF.slider_templates["OPTIONS_SLIDER_TEMPLATE"] = { thumbcolor = {0, 0, 0, 0.5}, } +---install a template +---@param widgetType string +---@param templateName string +---@param template table +---@param parentName any +---@return table function DF:InstallTemplate(widgetType, templateName, template, parentName) local newTemplate = {} @@ -4597,12 +3682,27 @@ function DF:ReskinSlider(slider, heightOffset) end end +function DF:GetCurrentClassName() + local className = UnitClass("player") + return className +end + +function DF:GetCurrentSpecName() + local specIndex = DF.GetSpecialization() + if (specIndex) then + local specId, specName = DF.GetSpecializationInfo(specIndex) + if (specId and specId ~= 0) then + return specName + end + end +end + function DF:GetCurrentSpec() local specIndex = DF.GetSpecialization() if (specIndex) then - local specID = DF.GetSpecializationInfo(specIndex) - if (specID and specID ~= 0) then - return specID + local specId = DF.GetSpecializationInfo(specIndex) + if (specId and specId ~= 0) then + return specId end end end @@ -4627,15 +3727,19 @@ local specs_per_class = { ["EVOKER"] = {1467, 1468, 1473}, } -function DF:GetClassSpecIDs(class) - return specs_per_class [class] + +function DF:GetClassSpecIDs(engClass) + return specs_per_class[engClass] +end +function DF:GetClassSpecIds(engClass) --naming conventions + return DF:GetClassSpecIDs(engClass) end local dispatch_error = function(context, errortext) DF:Msg( (context or "") .. " |cFFFF9900error|r: " .. (errortext or "")) end ---safe call an external func with payload and without telling who is calling +--call a function with payload, if the callback doesn't exists, quit silently function DF:QuickDispatch(func, ...) if (type(func) ~= "function") then return @@ -4678,7 +3782,7 @@ end --[=[ DF:CoreDispatch(func, context, ...) - safe call a function making a error window with what caused, the context and traceback of the error + safe call a function making an error window with what caused, context and traceback of the error this func is only used inside the framework for sensitive calls where the func must run without errors @func = the function which will be called @context = what made the function be called @@ -4721,44 +3825,42 @@ DF.ClassIndexToFileName = { DF.ClassFileNameToIndex = { - ["DEATHKNIGHT"] = 6, ["WARRIOR"] = 1, + ["PALADIN"] = 2, + ["HUNTER"] = 3, ["ROGUE"] = 4, - ["MAGE"] = 8, ["PRIEST"] = 5, - ["HUNTER"] = 3, - ["WARLOCK"] = 9, - ["DEMONHUNTER"] = 12, + ["DEATHKNIGHT"] = 6, ["SHAMAN"] = 7, - ["DRUID"] = 11, + ["MAGE"] = 8, + ["WARLOCK"] = 9, ["MONK"] = 10, - ["PALADIN"] = 2, + ["DRUID"] = 11, + ["DEMONHUNTER"] = 12, ["EVOKER"] = 13, } DF.ClassCache = {} function DF:GetClassList() - if (next (DF.ClassCache)) then return DF.ClassCache end for className, classIndex in pairs(DF.ClassFileNameToIndex) do - local classTable = C_CreatureInfo.GetClassInfo (classIndex) + local classTable = C_CreatureInfo.GetClassInfo(classIndex) if classTable then local t = { ID = classIndex, Name = classTable.className, Texture = [[Interface\GLUES\CHARACTERCREATE\UI-CharacterCreate-Classes]], - TexCoord = CLASS_ICON_TCOORDS [className], + TexCoord = CLASS_ICON_TCOORDS[className], FileString = className, } - tinsert(DF.ClassCache, t) + table.insert(DF.ClassCache, t) end end return DF.ClassCache - end --hardcoded race list @@ -4822,13 +3924,13 @@ function DF:GetCharacterRaceList() end for i = 1, 100 do - local raceInfo = C_CreatureInfo.GetRaceInfo (i) + local raceInfo = C_CreatureInfo.GetRaceInfo(i) if (raceInfo and DF.RaceList [raceInfo.raceID]) then tinsert(DF.RaceCache, {Name = raceInfo.raceName, FileString = raceInfo.clientFileString, ID = raceInfo.raceID}) end if IS_WOW_PROJECT_MAINLINE then - local alliedRaceInfo = C_AlliedRaces.GetRaceInfoByID (i) + local alliedRaceInfo = C_AlliedRaces.GetRaceInfoByID(i) if (alliedRaceInfo and DF.AlliedRaceList [alliedRaceInfo.raceID]) then tinsert(DF.RaceCache, {Name = alliedRaceInfo.maleName, FileString = alliedRaceInfo.raceFileString, ID = alliedRaceInfo.raceID}) end @@ -4841,24 +3943,72 @@ end --get a list of talents for the current spec the player is using --if onlySelected return an index table with only the talents the character has selected --if onlySelectedHash return a hash table with [spelID] = true -function DF:GetCharacterTalents (onlySelected, onlySelectedHash) +function DF:GetCharacterTalents(bOnlySelected, bOnlySelectedHash) local talentList = {} - - for i = 1, 7 do - for o = 1, 3 do - local talentID, name, texture, selected, available = GetTalentInfo (i, o, 1) - if (onlySelectedHash) then - if (selected) then - talentList [talentID] = true - break + local version, build, date, tocversion = GetBuildInfo() + + if (tocversion >= 70000 and tocversion <= 99999) then + for i = 1, 7 do + for o = 1, 3 do + local talentID, name, texture, selected, available = GetTalentInfo(i, o, 1) + if (bOnlySelectedHash) then + if (selected) then + talentList[talentID] = true + break + end + elseif (bOnlySelected) then + if (selected) then + table.insert(talentList, {Name = name, ID = talentID, Texture = texture, IsSelected = selected}) + break + end + else + table.insert(talentList, {Name = name, ID = talentID, Texture = texture, IsSelected = selected}) end - elseif (onlySelected) then - if (selected) then - tinsert(talentList, {Name = name, ID = talentID, Texture = texture, IsSelected = selected}) - break + end + end + + elseif (tocversion >= 100000) then + if (not bOnlySelected) then + return DF:GetAllTalents() + end + + local configId = C_ClassTalents.GetActiveConfigID() + if (configId) then + local configInfo = C_Traits.GetConfigInfo(configId) + --get the spells from the SPEC from talents + for treeIndex, treeId in ipairs(configInfo.treeIDs) do + local treeNodes = C_Traits.GetTreeNodes(treeId) + + for nodeIdIndex, treeNodeID in ipairs(treeNodes) do + local traitNodeInfo = C_Traits.GetNodeInfo(configId, treeNodeID) + + if (traitNodeInfo) then + local activeEntry = traitNodeInfo.activeEntry + local entryIds = traitNodeInfo.entryIDs + + for i = 1, #entryIds do + local entryId = entryIds[i] --number + local traitEntryInfo = C_Traits.GetEntryInfo(configId, entryId) + local borderTypes = Enum.TraitNodeEntryType + + if (traitEntryInfo.type) then -- == borderTypes.SpendCircle + local definitionId = traitEntryInfo.definitionID + local traitDefinitionInfo = C_Traits.GetDefinitionInfo(definitionId) + local spellId = traitDefinitionInfo.overriddenSpellID or traitDefinitionInfo.spellID + local spellName, _, spellTexture = GetSpellInfo(spellId) + local bIsSelected = (activeEntry and activeEntry.rank and activeEntry.rank > 0) or false + if (spellName and bIsSelected) then + local talentInfo = {Name = spellName, ID = spellId, Texture = spellTexture, IsSelected = true} + if (bOnlySelectedHash) then + talentList[spellId] = talentInfo + else + table.insert(talentList, talentInfo) + end + end + end + end + end end - else - tinsert(talentList, {Name = name, ID = talentID, Texture = texture, IsSelected = selected}) end end end @@ -4866,7 +4016,7 @@ function DF:GetCharacterTalents (onlySelected, onlySelectedHash) return talentList end -function DF:GetCharacterPvPTalents (onlySelected, onlySelectedHash) +function DF:GetCharacterPvPTalents(onlySelected, onlySelectedHash) if (onlySelected or onlySelectedHash) then local talentsSelected = C_SpecializationInfo.GetAllSelectedPvpTalentIDs() local talentList = {} @@ -5130,6 +4280,15 @@ DF.SpecListByClass = { }, } +---return if the specId is a valid spec, it'll return false for specIds from the tutorial area +---@param self table +---@param specId number +function DF:IsValidSpecId(specId) + local _, class = UnitClass("player") + local specs = DF.ClassSpecs[class] + return specs and specs[specId] and true or false +end + --given a class and a specId, return if the specId is a spec from the class passed function DF:IsSpecFromClass(class, specId) return DF.ClassSpecs[class] and DF.ClassSpecs[class][specId] @@ -5633,6 +4792,8 @@ function DF:DebugVisibility(UIObject) local bIsShown = UIObject:IsShown() print("Is Shown:", bIsShown and "|cFF00FF00true|r" or "|cFFFF0000false|r") + print("Alpha > 0:", UIObject:GetAlpha() > 0 and "|cFF00FF00true|r" or "|cFFFF0000false|r") + local bIsVisible = UIObject:IsVisible() print("Is Visible:", bIsVisible and "|cFF00FF00true|r" or "|cFFFF0000false|r") @@ -5642,4 +4803,25 @@ function DF:DebugVisibility(UIObject) local numPoints = UIObject:GetNumPoints() print("Num Points:", numPoints > 0 and "|cFF00FF00" .. numPoints .. "|r" or "|cFFFF00000|r") -end \ No newline at end of file +end + +local benchmarkTime = 0 +local bBenchmarkEnabled = false +function _G.__benchmark(bNotPrintResult) + if (not bBenchmarkEnabled) then + bBenchmarkEnabled = true + debugprofilestop() + benchmarkTime = debugprofilestop() + else + local elapsed = debugprofilestop() - benchmarkTime + bBenchmarkEnabled = false + + if (bNotPrintResult) then + return elapsed + end + + print("Elapsed Time:", elapsed) + return elapsed + end +end + diff --git a/libs/DF/header.lua b/libs/DF/header.lua index a0b7b907..ec74ed27 100644 --- a/libs/DF/header.lua +++ b/libs/DF/header.lua @@ -638,8 +638,8 @@ local default_header_options = { ---each column is placed on the right side of the previous column. ---@param parent frame ---@param headerTable table ----@param options table|nil ----@param frameName string|nil +---@param options table? +---@param frameName string? ---@return df_headerframe function detailsFramework:CreateHeader(parent, headerTable, options, frameName) ---create the header frame which is returned by this function @@ -649,6 +649,7 @@ function detailsFramework:CreateHeader(parent, headerTable, options, frameName) detailsFramework:Mixin(newHeader, detailsFramework.OptionsFunctions) detailsFramework:Mixin(newHeader, detailsFramework.HeaderMixin) + options = options or {} newHeader:BuildOptionsTable(default_header_options, options) --set the backdrop and backdrop color following the values in the options table diff --git a/libs/DF/icongeneric.lua b/libs/DF/icongeneric.lua index bfc6de6e..887de83c 100644 --- a/libs/DF/icongeneric.lua +++ b/libs/DF/icongeneric.lua @@ -12,6 +12,11 @@ end ---@field CountdownText fontstring ---@field CooldownEdge texture ---@field options table +---@field NextIcon number +---@field IconPool table table which store the icons created for this iconrow +---@field SetAuraWithIconTemplate fun(self:df_icongeneric, aI:aurainfo, iconTemplateTable:table) +---@field ClearIcons fun(self:df_icongeneric, resetBuffs:boolean?, resetDebuffs:boolean?) +---@field AlignAuraIcons fun(self:df_icongeneric) local unpack = unpack local CreateFrame = CreateFrame @@ -23,6 +28,10 @@ local spellNameCache = {} local emptyTable = {} local white = {1, 1, 1, 1} +local sortIconByShownState = function(i1, i2) + return i1:IsShown() and not i2:IsShown() +end + local iconFrameOnHideScript = function(self) if (self.cooldownLooper) then self.cooldownLooper:Cancel() @@ -161,6 +170,15 @@ detailsFramework.IconGenericMixin = { self:AddSpecificIcon(iconTemplateTable.id, iconTemplateTable.id, nil, iconTemplateTable.startTime, iconTemplateTable.duration, nil, nil, iconTemplateTable.count, nil, nil, nil, nil, nil, nil, iconTemplateTable) end, + IsIconShown = function(self, identifierKey) + if (not identifierKey or identifierKey == "") then + return + end + if (self.AuraCache[identifierKey]) then + return true + end + end, + ---set an icon frame with a template ---@param self df_iconrow the parent frame ---@param aI aurainfo @@ -551,7 +569,8 @@ detailsFramework.IconGenericMixin = { if iconAmount == 0 then self:Hide() else - table.sort(iconPool, function(i1, i2) return i1:IsShown() and not i2:IsShown() end) + table.sort(iconPool, sortIconByShownState) + local shownAmount = 0 for i = 1, iconAmount do if iconPool[i]:IsShown() then diff --git a/libs/DF/keybind.lua b/libs/DF/keybind.lua new file mode 100644 index 00000000..ba6e1677 --- /dev/null +++ b/libs/DF/keybind.lua @@ -0,0 +1,1548 @@ +---@type detailsframework +local detailsFramework = _G["DetailsFramework"] +if (not detailsFramework or not DetailsFrameworkCanLoad) then + return +end + +--[=[[ + bugs: + -t verificar se esta mostrando o indicador de keybinds repetidas + -t fazer o sort de maneira que fique melhor + -t fazer um indicador dizendo que a keybind esta desabilitada por causa da load condition + -t fazer o debug do puf mostrar as keybinds (string) + - quando iniciar uma edição, fazer um indicador the diga que aquela linha esta esta sendo editada + - transferir o montagem do código seguro das keybinds no puf para o framework + +tried to edit a spell binding: +2x Details/Libs/DF/keybind.lua:1080: attempt to index local 'actionId' (a number value) +[string "@Details/Libs/DF/keybind.lua"]:1160: in function
+[string "=[C]"]: in function `xpcall' +[string "@Details/Libs/DF/fw.lua"]:4864: in function `CoreDispatch' +[string "@Details/Libs/DF/button.lua"]:720: in function
+--]=] + +local _ +local IsShiftKeyDown = _G["IsShiftKeyDown"] +local IsControlKeyDown = _G["IsControlKeyDown"] +local IsAltKeyDown = _G["IsAltKeyDown"] +local CreateFrame = _G["CreateFrame"] +local GetSpellInfo = _G["GetSpellInfo"] +local unpack = unpack ---@diagnostic disable-line + +---@alias actionidentifier string a string in the format of "spell-spellId" or "macro-macroName" or "system-target", etc, used to pass information about the action more easily + +---@class keybind_scroll_data : {key1:string, key2:any, key3:any, key4:string, key5:boolean, key6:number, key7:string} + +---@class df_keybind : table +---@field name string +---@field action string|number +---@field keybind string +---@field macro string +---@field conditions table +---@field icon any + +---@class df_editkeybindframe : frame +---@field bIsEditing boolean +---@field actionIdentifier actionidentifier +---@field conditionsFailLoadReasonText fontstring +---@field keybindTable df_keybind +---@field nameEditBox df_textentry +---@field iconPickerButton df_button +---@field conditionsButton df_button +---@field editMacroEditBox df_luaeditor +---@field cancelButton df_button +---@field saveButton df_button +---@field deleteMacroButton df_button +---@field Disable fun(self:df_editkeybindframe) +---@field Enable fun(self:df_editkeybindframe) + +---@class df_selectkeybindbutton : button +---@field actionIdentifier actionidentifier +---@field keybindTable df_keybind +---@field keybindScrollData keybind_scroll_data + +---@class df_keybindscrollline : frame, df_headerfunctions +---@field bIsSeparator boolean +---@field keybindScrollLine boolean +---@field backgroundTexture texture +---@field highlightTexture texture +---@field separatorTitleText fontstring +---@field spellIconTexture texture +---@field actionNameFontString fontstring +---@field setKeybindButton df_button +---@field clearKeybindButton df_button +---@field editKeybindSettingsButton df_button +---@field SetAsSeparator function + +local keybindPrototype = { + name = "", --a name or alias to call this keybind + action = "", --which action this keybind will do, can be a spellId for spell casting, a macro text or targetting like "target", "focus", "togglemenu" + keybind = "", + macro = "", + conditions = detailsFramework:UpdateLoadConditionsTable({}), + icon = "", +} + +---@type {action: string, keybind: string, icon: string, name: string}[] +local defaultMouseKeybinds = { + {action = "target", name = _G["TARGET"], keybind = "type1", icon = [[Interface\MINIMAP\TRACKING\Target]]}, --default: left mouse button + {action = "togglemenu", name = _G["SLASH_TEXTTOSPEECH_MENU"], keybind = "type2", icon = [[Interface\BUTTONS\UI-GuildButton-PublicNote-Up]]}, --default: right mouse button + {action = "focus", name = _G["FOCUS"], keybind = "type3", icon = [[Interface\MINIMAP\TRACKING\Focus]]} --default: middle mouse button +} + +local defaultMouseKeybindsKV = { + ["target"] = defaultMouseKeybinds[1], + ["togglemenu"] = defaultMouseKeybinds[2], + ["focus"] = defaultMouseKeybinds[3], +} + +---@class df_keybindscroll : df_scrollbox +---@field UpdateScroll fun(self:df_keybindscroll) + +---@class df_keybindframe : frame, df_optionsmixin +---@field options table +---@field data table +---@field keybindData table +---@field keybindScrollData keybind_scroll_data +---@field Header df_headerframe +---@field actionId number? the actionId is the spell Id or an actionId or a macro text +---@field button button? the button which got clicked to start editing a keybind +---@field bIsListening boolean if the frame is wayting the user to press a keybind (listening key inputs) +---@field bIsKeybindFrame boolean +---@field keybindScroll df_keybindscroll +---@field keybindListener frame +---@field editKeybindFrame df_editkeybindframe +---@field callback function +---@field ClearKeybind fun(self:button, buttonPresed:string, actionIdentifier:actionidentifier, keybindTable:any) +---@field CreateKeybindScroll fun(self:df_keybindframe) +---@field CreateKeybindListener fun(self:df_keybindframe) +---@field CreateEditPanel fun(self:df_keybindframe) +---@field CreateKeybindScrollLine fun(self:df_keybindframe, index:number) +---@field DeleteMacro fun(self:df_keybindframe) +---@field FindKeybindTable fun(self:df_keybindframe, keybindType:string, actionId:any, actionIdentifier:actionidentifier?) : df_keybind?, number? +---@field GetEditPanel fun(self:df_keybindframe) : df_editkeybindframe +---@field GetPressedModifiers fun() : string +---@field GetListeningActionId fun(self:df_keybindframe) : number +---@field GetListeningState fun(self:df_keybindframe) : boolean, any, button, keybind_scroll_data +---@field GetKeybindData fun(self:df_keybindframe) : df_keybind[] +---@field GetKeybindListener fun(self:df_keybindframe) : frame +---@field GetKeybindScroll fun(self:df_keybindframe) : df_keybindscroll +---@field GetKeybindCallback fun(self:df_keybindframe):function +---@field GetKeybindModifiers fun(keybind:string) : string +---@field GetKeybindTypeAndActionFromIdentifier fun(self:df_keybindframe, actionIdentifier:actionidentifier) : string, any +---@field IsListening fun(self:df_keybindframe) : boolean +---@field IsEditingKeybindSettings fun(self:df_keybindframe) : boolean, string, df_keybind +---@field IsKeybindActionMacro fun(self:df_keybindframe, actionId:any) : boolean +---@field CallKeybindChangeCallback fun(self:df_keybindframe, type:string, keybindTable:df_keybind?, keybindPressed:string?, removedIndex:number?, macroText:string?) +---@field OnKeybindNameChange fun(self:df_keybindframe, name:string) +---@field OnKeybindMacroChange fun(self:df_keybindframe, macroText:string) +---@field OnKeybindIconChange fun(self:df_keybindframe, iconTexture:string) +---@field OnUserClickedToChooseKeybind fun(self:df_keybindframe, button:button, actionIdentifier:actionidentifier, keybindTable:df_keybind|false) +---@field OnUserPressedKeybind fun(self:df_keybindframe, key:string) +---@field SaveKeybindToKeybindData fun(self:df_keybindframe, actionId:any, pressedKeybind:any, bJustCreated:boolean) +---@field SetClearButtonsEnabled fun(self:df_keybindframe, enabled:boolean) +---@field SetEditButtonsEnabled fun(self:df_keybindframe, enabled:boolean) +---@field SetListeningState fun(self:df_keybindframe, value:boolean, actionIdentifier:actionidentifier?, button:button?, keybindScrollData:keybind_scroll_data?) +---@field SetKeybindData fun(self:df_keybindframe, keybindData:table) +---@field SetKeybindCallback fun(self:df_keybindframe, callback:function) +---@field StartEditingKeybindSettings fun(self:frame, button:string, actionIdentifier:actionidentifier, keybindTable:df_keybind) +---@field StopEditingKeybindSettings fun(self:df_keybindframe) +---@field SwitchSpec fun(self:button, button:string, newSpecId:number) + +detailsFramework:NewColor("BLIZZ_OPTIONS_COLOR", 1, 0.8196, 0, 1) + +local DARK_BUTTON_TEMPLATE = detailsFramework:InstallTemplate("button", "DARK_BUTTON_TEMPLATE", {backdropcolor = {.1, .1, .1, .98}}, "OPTIONS_BUTTON_TEMPLATE") + +---only called from OnUserPressedKeybind() when the a keybindTable is not found for the action +---@return df_keybind +local createNewKeybindTable = function(name, keybind, macro, actionId, iconTexture) + ---@type df_keybind + local newMacroTable = detailsFramework.table.copy({}, keybindPrototype) + newMacroTable.name = name or "My New Macro" --if a name isn't passed, it's a macro + newMacroTable.keybind = keybind or "" + newMacroTable.macro = macro or "" + newMacroTable.action = actionId + newMacroTable.icon = iconTexture or "" + return newMacroTable +end + +---return a number representing the sort order of a spell +---@param keybindData any +---@param spellName string +---@param bIsAvailable any +---@return number +local getSpellSortOrder = function(keybindData, spellName, bIsAvailable) + local sortScore = 0 + + if (not bIsAvailable) then + sortScore = sortScore + 5000 + end + + if (not keybindData) then + sortScore = sortScore + 300 + end + + sortScore = sortScore + string.byte(spellName) + return sortScore +end + +local default_options = { + width = 580, + height = 500, + edit_width = 400, + edit_height = 0, + scroll_width = 580, + scroll_height = 480, + amount_lines = 18, + line_height = 26, + show_spells = true, + show_unitcontrols = true, + show_macros = true, + can_modify_keybind_data = true, --if false, won't change the data table passed on the constructor or the one returned by GetKeybindData +} + +local headerTable = { + {text = "", width = 34}, --spell icon + {text = "", width = 200}, + {text = "Keybind", width = 260}, + {text = "Clear", width = 40}, + {text = "Edit", width = 40}, +} +local headerOptions = { + padding = 2, + backdrop_color = {0, 0, 0, 0}, +} + + +local ignoredKeys = { + ["LSHIFT"] = true, + ["RSHIFT"] = true, + ["LCTRL"] = true, + ["RCTRL"] = true, + ["LALT"] = true, + ["RALT"] = true, + ["UNKNOWN"] = true, +} + +local mouseButtonToClickType = { + ["LeftButton"] = "type1", + ["RightButton"] = "type2", + ["MiddleButton"] = "type3", + ["Button4"] = "type4", + ["Button5"] = "type5", + ["Button6"] = "type6", + ["Button7"] = "type7", + ["Button8"] = "type8", + ["Button9"] = "type9", + ["Button10"] = "type10", + ["Button11"] = "type11", + ["Button12"] = "type12", + ["Button13"] = "type13", + ["Button14"] = "type14", + ["Button15"] = "type15", + ["Button16"] = "type16", +} + +local roundedCornerPreset = { + roundness = 5, + color = {.075, .075, .075, 1}, + border_color = {.05, .05, .05, 1}, + horizontal_border_size_offset = 8, +} + +--> helpers +local getMainFrame = function(UIObject) + if (UIObject.bIsKeybindFrame) then + return UIObject + end + + local parentFrame = UIObject:GetParent() + + for i = 1, 5 do + if (parentFrame.bIsKeybindFrame) then + return parentFrame + end + parentFrame = parentFrame:GetParent() + end +end + +local setAsSeparator = function(line, bIsSeparator, titleText) + if (bIsSeparator) then + line.spellIconTexture:Hide() + line.actionNameFontString:Hide() + line.setKeybindButton:Hide() + line.clearKeybindButton:Hide() + line.editKeybindSettingsButton:Hide() + line.separatorTitleText:Show() + line.separatorTitleText:SetText(titleText) + line.bIsSeparator = true + line.backgroundTexture:Hide() + else + line.spellIconTexture:Show() + line.actionNameFontString:Show() + line.setKeybindButton:Show() + line.clearKeybindButton:Show() + line.editKeybindSettingsButton:Show() + line.separatorTitleText:Hide() + line.bIsSeparator = false + line.backgroundTexture:Show() + end +end + +---@class df_keybindmixin +detailsFramework.KeybindMixin = { + bIsListening = false, + CurrentKeybindEditingSet = {}, + + ---and the player change spec, the list of spells will change, hence need to update the keybind list if the panel is open + --if isn't open, the keybinds will be updated when the panel is opened + OnSpecChanged = function(self, button, newSpecId) --switch_spec + --quick hide and show as a feedback to the player that the spec was changed + --C_Timer.After (.04, function() EnemyGridOptionsPanelFrameKeybindScroill:Hide() end) --!need to defined the scroll frame + --C_Timer.After (.06, function() EnemyGridOptionsPanelFrameKeybindScroill:Show() end) --!need to defined the scroll frame + --EnemyGridOptionsPanelFrameKeybindScroill:UpdateScroll() --!need to defined the scroll frame + end, + + ---return true if the keybindFrame is waiting for the player to press a keybind + ---@param self df_keybindframe + ---@return boolean bIsListening + IsListening = function(self) + return self.bIsListening + end, + + ---return the actionId which the frame is currently listening for a keybind + ---@param self df_keybindframe + GetListeningActionId = function(self) + return self.actionId + end, + + ---set the keybindFrame to listen for a keybind + ---@param self df_keybindframe + ---@param value boolean + ---@param actionId number? + ---@param button button? + SetListeningState = function(self, value, actionId, button, keybindScrollData) + self.bIsListening = value + self.actionId = actionId + self.button = button + self.keybindScrollData = keybindScrollData + + self:SetClearButtonsEnabled(not value) + self:SetEditButtonsEnabled(not value) + end, + + ---get the listening state + ---@param self df_keybindframe + ---@return boolean + ---@return number + ---@return button + ---@return keybind_scroll_data + GetListeningState = function(self) + return self.bIsListening, self.actionId, self.button, self.keybindScrollData + end, + + ---return the frame which wait for keybinds + ---@param self df_keybindframe + ---@return frame + GetKeybindListener = function(self) + return self.keybindListener + end, + + ---get the scroll frame + ---@param self df_keybindframe + ---@return df_keybindscroll + GetKeybindScroll = function(self) + return self.keybindScroll + end, + + ---keybind listener is the frame which reads the key pressed by the player when setting a keybind for an ability + CreateKeybindListener = function(self) + local keybindListener = CreateFrame ("frame", nil, self, "BackdropTemplate") + keybindListener:SetFrameStrata("tooltip") + keybindListener:SetSize(200, 60) + detailsFramework:ApplyStandardBackdrop(keybindListener) + + self.keybindListener = keybindListener + + keybindListener.text = detailsFramework:CreateLabel(keybindListener, "- Press a keyboard key to bind.\n- Click to bind a mouse button.\n- Press escape to cancel.", 11, "orange") + keybindListener.text:SetPoint("center", keybindListener, "center", 0, 0) + keybindListener:Hide() + end, + + ---callback from the clear button + ---@param self button + ---@param button any + ---@param actionIdentifier string + ---@param keybindTable any + ClearKeybind = function(self, button, actionIdentifier, keybindTable) --~clear + if (not keybindTable) then + return + end + + ---@type df_keybindframe + local keyBindFrame = getMainFrame(self) + + local keybindType, actionId = keyBindFrame:GetKeybindTypeAndActionFromIdentifier(actionIdentifier) + local _, index = keyBindFrame:FindKeybindTable(keybindType, actionId, actionIdentifier) + + if (index) then + if (keybindType == "macro") then + keybindTable.keybind = "" + else + if (keyBindFrame.options.can_modify_keybind_data) then + local keybindData = keyBindFrame:GetKeybindData() + table.remove(keybindData, index) + end + keyBindFrame:CallKeybindChangeCallback("removed", nil, nil, index) + end + end + + local bIsEditingKeybind = keyBindFrame:IsEditingKeybindSettings() + if (bIsEditingKeybind) then + keyBindFrame:StopEditingKeybindSettings() + end + + local keybindScroll = keyBindFrame:GetKeybindScroll() + keybindScroll:UpdateScroll() + end, + + ---@param self df_keybindframe + DeleteMacro = function(self) + local bIsEditingKeybind, actionIdentifier, keybindTable = self:IsEditingKeybindSettings() + if (not bIsEditingKeybind) then + return + end + + if (not self:IsKeybindActionMacro(keybindTable.action)) then + return + end + + local _, index = self:FindKeybindTable("macro", keybindTable.action, actionIdentifier) + + if (index) then + if (self.options.can_modify_keybind_data) then + local keybindData = self:GetKeybindData() + table.remove(keybindData, index) + end + + self:CallKeybindChangeCallback("removed", nil, nil, index) + end + + self:StopEditingKeybindSettings() + + local keybindScroll = self:GetKeybindScroll() + keybindScroll:UpdateScroll() + end, + + ---return a string with the modifiers of a keybind + ---@param keybind string + ---@return string + GetKeybindModifiers = function(keybind) + local modifier = "" + keybind = string.upper(keybind) + + if (keybind:find("SHIFT-")) then + modifier = "SHIFT-" + end + + if (keybind:find("CTRL-")) then + modifier = modifier .. "CTRL-" + end + + if (keybind:find("ALT-")) then + modifier = modifier .. "ALT-" + end + + return modifier + end, + + ---return a string with the modifiers of the key pressed by the player + ---@return string + GetPressedModifiers = function() + return (IsShiftKeyDown() and "SHIFT-" or "") .. (IsControlKeyDown() and "CTRL-" or "") .. (IsAltKeyDown() and "ALT-" or "") + end, + + ---comment + ---@param self df_keybindframe + ---@param keybindTable df_keybind + ---@param pressedKeybind any + ---@param bKeybindJustCreated boolean + SaveKeybindToKeybindData = function(self, keybindTable, pressedKeybind, bKeybindJustCreated) + local keybindData = self:GetKeybindData() --from savedVariables + + --if the keybindTable was created by the function which called this function, then need to add the keybind into the saved variables table + if (bKeybindJustCreated) then + table.insert(keybindData, keybindTable) + end + + keybindTable.keybind = pressedKeybind + end, + + ---return the keybindData table if exists + ---@param self df_keybindframe + ---@param keybindType string + ---@param actionId any + ---@param actionIdentifier actionidentifier? + ---@return df_keybind?, number? + FindKeybindTable = function(self, keybindType, actionId, actionIdentifier) + local keybindData = self:GetKeybindData() + + if (keybindType == "spell" or keybindType == "system") then + for i = 1, #keybindData do + local keybindTable = keybindData[i] + if (keybindTable.action == actionId) then + return keybindTable, i + end + end + end + + if (keybindType == "macro") then + for i = 1, #keybindData do + local keybindTable = keybindData[i] + if (keybindTable.action == actionIdentifier) then + return keybindTable, i + end + end + end + end, + + ---comment + ---@param self df_keybindframe + ---@param actionIdentifier any + ---@return string + ---@return string|number + GetKeybindTypeAndActionFromIdentifier = function(self, actionIdentifier) + ---@type string, string + local keybindType, actionId = actionIdentifier:match("([^%-]+)%-(.+)") + if (keybindType == "spell") then + return keybindType, tonumber(actionId) and tonumber(actionId) or 0 + end + return keybindType, actionId + end, + + ---when the user selected a keybind for an action, this function is called + ---@param self df_keybindframe + ---@param keyPressed any keyboard or mouse key to be used to perform the choosen action + OnUserPressedKeybind = function(self, keyPressed) --called from OnUserClickedToChooseKeybind and from OnKeyDown() script + --if the player presses a control key, ignore it + if (ignoredKeys[keyPressed]) then + return + end + + local keybindListener = self:GetKeybindListener() + + --exit the process if 'esc' is pressed + if (keyPressed == "ESCAPE") then + self:SetListeningState(false) + keybindListener:Hide() + self:SetScript("OnKeyDown", nil) + return + end + + local modifiers = self:GetPressedModifiers() + local pressedKeybind = modifiers .. keyPressed + local bIsListening, actionIdentifier, button, keybindScrollData = self:GetListeningState() + local bKeybindJustCreated = false + + --keybindType can be 'macro', 'spell' or 'system' + --actionId can be a spellId, a macro name or some other action like 'target' 'focus' 'togglemenu' + local keybindType, actionId = self:GetKeybindTypeAndActionFromIdentifier(actionIdentifier) + local keybindTable = self:FindKeybindTable(keybindType, actionId, actionIdentifier) + + if (not keybindTable) then + local iconTexture = keybindScrollData[2] + --create a new keybindTable + if (keybindType == "spell") then + local spellId = actionId + local spellName = GetSpellInfo(spellId) + if (spellName) then + --actionId is the spellId + keybindTable = createNewKeybindTable(spellName, pressedKeybind, "", actionId, iconTexture) + end + + elseif (keybindType == "system") then + local defaultKeybind = defaultMouseKeybindsKV[actionId] + --actionId is an action like 'target' 'focus' 'togglemenu' + keybindTable = createNewKeybindTable(defaultKeybind.name, pressedKeybind, "", actionId, iconTexture) + + elseif (keybindType == "macro") then + local macroName = "New Macro" + --actionId is the word 'macro' + keybindTable = createNewKeybindTable(macroName, pressedKeybind, "/say hi", "macro", iconTexture) + end + + bKeybindJustCreated = true + end + + if (self.options.can_modify_keybind_data) then + --if the options for this frame allows it to change the keybind in the addon savedVariables, then do it + self:SaveKeybindToKeybindData(keybindTable, pressedKeybind, bKeybindJustCreated) + end + + self:CallKeybindChangeCallback("modified", keybindTable, pressedKeybind) + + self:SetListeningState(false) + self:SetScript("OnKeyDown", nil) + keybindListener:Hide() + + --dumpt({"modifier", modifier, "newKeybind", newKeybind, "actionId", actionId}) + + local keybindScroll = self:GetKeybindScroll() + keybindScroll:UpdateScroll() + end, + + ---callback for when the user click in to define a keybind to an action + ---@param self df_selectkeybindbutton + ---@param button any + ---@param actionIdentifier actionidentifier + ---@param keybindTable df_keybindframe + OnUserClickedToChooseKeybind = function(self, button, actionIdentifier, keybindTable) + ---@type df_keybindframe + local keyBindFrame = getMainFrame(self) + local bIsListening, _, frameButton = keyBindFrame:GetListeningState() + + --if the listener is already listening for a keybind while the player clicks on another OnUserClickedToChooseKeybind button, then cancel the previous listener + if (bIsListening and (self == frameButton)) then + --if the frame is already listening, it could be a mouse click to set the keybind + local clickType = mouseButtonToClickType[button] + keyBindFrame:OnUserPressedKeybind(clickType) + return + end + + bIsListening = true + + local keybindScrollData = self.keybindScrollData + keyBindFrame:SetListeningState(bIsListening, actionIdentifier, self, keybindScrollData) + keyBindFrame:SetScript("OnKeyDown", keyBindFrame.OnUserPressedKeybind) + + local keybindListener = keyBindFrame:GetKeybindListener() + keybindListener:ClearAllPoints() + keybindListener:SetPoint("bottom", self, "top", 0, 0) + keybindListener:Show() + + local bIsEditingKeybind = keyBindFrame:IsEditingKeybindSettings() + if (bIsEditingKeybind) then + keyBindFrame:StopEditingKeybindSettings() + end + end, + + SetClearButtonsEnabled = function(self, bIsEnabled) + local keybindScroll = self:GetKeybindScroll() + local lines = keybindScroll:GetLines() + for i = 1, #lines do + local line = lines[i] + if (bIsEnabled) then + --can only set enabled if the keybind isn't empty + if (line.setKeybindButton.text ~= "") then + line.clearKeybindButton:Enable() + end + else + line.clearKeybindButton:Disable() + end + end + end, + + SetEditButtonsEnabled = function(self, bIsEnabled) + local keybindScroll = self:GetKeybindScroll() + local lines = keybindScroll:GetLines() + for i = 1, #lines do + local line = lines[i] + if (bIsEnabled) then + --can only set enabled if the keybind isn't empty + if (line.setKeybindButton.text ~= "") then + line.editKeybindSettingsButton:Enable() + end + else + line.editKeybindSettingsButton:Disable() + end + end + end, + + RefreshKeybindScroll = function(self, scrollData, offset, totalLines) --~refresh + local keyBindFrame = getMainFrame(self) + + ---@type table + local repeatedKeybinds = {} + + ---@cast scrollData keybind_scroll_data[] + + --build a list of repeated keybinds + for index = 1, #scrollData do + local keybindScrollData = scrollData[index] + local actionName, iconTexture, actionId, keybindData, bIsAvailable = unpack(keybindScrollData) + ---@cast keybindData df_keybind + + if (bIsAvailable) then + if (type(keybindData) == "table" and keybindData.keybind and keybindData.keybind ~= "") then + repeatedKeybinds[keybindData.keybind] = repeatedKeybinds[keybindData.keybind] or {} + table.insert(repeatedKeybinds[keybindData.keybind], keybindData) + end + end + end + + --local bIsListening = keyBindFrame:GetListeningState() + --local bIsEditingKeybind = keyBindFrame:IsEditingKeybindSettings() + + local lastKeybindActionType = "" + + --refresh the scroll bar + for i = 1, totalLines do + local index = i + offset + ---@type keybind_scroll_data + local keybindScrollData = scrollData[index] + + if (keybindScrollData) then + local line = self:GetLine(i) + + ---@type string, number, any, df_keybind|false, boolean, number, string + local actionName, iconTexture, actionId, keybindTable, bIsAvailable, sortNumber, actionIdentifier = unpack(keybindScrollData) + + if (actionName == "@separator") then + line:SetAsSeparator(true, iconTexture) + else + line:SetAsSeparator(false) + --if the keybindData doesn't exists, means the user did not set a keybind for this action yet + --in this case keybindData is a false boolean + --keydindText is the text showing which keyboard or mouse button need to be pressed to activate the action + --if the keybind isn't set, use an empty string + local keydindText = keybindTable and keybindTable.keybind or "" + + keydindText = keydindText:gsub("type1", _G["LEFT_BUTTON_STRING"]) + keydindText = keydindText:gsub("type2", _G["RIGHT_BUTTON_STRING"]) + keydindText = keydindText:gsub("type3", _G["MIDDLE_BUTTON_STRING"]) + + if (keydindText:match("type%d")) then + local buttonId = keydindText:match("type(%d)") + buttonId = tonumber(buttonId) + if (buttonId) then + local buttonName = _G["BUTTON_" .. buttonId .. "_STRING"] + if (buttonName and type(buttonName) == "string") then + keydindText = keydindText:gsub("type" .. buttonId, buttonName) + end + end + end + + keydindText = keydindText:gsub("%-", " - ") + + line.setKeybindButton.text = keydindText + + --start editing keybind button + line.setKeybindButton:SetClickFunction(keyBindFrame.OnUserClickedToChooseKeybind, actionIdentifier, keybindTable, "left") + line.setKeybindButton:SetClickFunction(keyBindFrame.OnUserClickedToChooseKeybind, actionIdentifier, keybindTable, "right") + + --clear keybind button + if (keydindText ~= "") then + line.clearKeybindButton:Enable() + line.clearKeybindButton:SetClickFunction(keyBindFrame.ClearKeybind, actionIdentifier, keybindTable) + line.editKeybindSettingsButton:Enable() + line.editKeybindSettingsButton:SetClickFunction(keyBindFrame.StartEditingKeybindSettings, actionIdentifier, keybindTable) + else + line.clearKeybindButton:Disable() + line.editKeybindSettingsButton:Disable() + end + + local setKeybindButtonWidget = line.setKeybindButton.widget + setKeybindButtonWidget.keybindScrollData = keybindScrollData + + line.spellIconTexture:SetTexture(iconTexture) + + if (not bIsAvailable) then + line.spellIconTexture:SetDesaturated(true) + line.setKeybindButton.widget:SetColor(0, 0, 0, 0.1) + detailsFramework:SetFontColor(line.actionNameFontString, "gray") + else + line.spellIconTexture:SetDesaturated(false) + line.setKeybindButton.widget:SetColor(unpack(roundedCornerPreset.color)) + detailsFramework:SetFontColor(line.actionNameFontString, "BLIZZ_OPTIONS_COLOR") + end + + line.spellIconTexture:SetTexCoord(.1, .9, .1, .9) + line.actionNameFontString:SetText(actionName) + + line.setKeybindButton.widget:SetBorderCornerColor(0, 0, 0, 0) + + --check for repeated keybind + if (keybindTable and bIsAvailable) then + local keybind = keybindTable.keybind + local keybindTables = repeatedKeybinds[keybind] + if (keybindTables and #keybindTables > 1) then + line.setKeybindButton.widget:SetBorderCornerColor(1, .68, 0, 1) + end + end + end + end + end + end, + + ---run when the mouse enters a scroll line + ---@param self df_keybindscrollline + OnEnterScrollLine = function(self) + local keyBindFrame = getMainFrame(self) + local editPanel = keyBindFrame:GetEditPanel() + editPanel.conditionsFailLoadReasonText:SetText("") + + if (self.bIsSeparator) then + return + end + + if (not self.keybindScrollLine) then + --when the mouse enters a child frame, the self is the child frame, not the scroll line + self = self:GetParent() ---@diagnostic disable-line getting the parent from df_keybindscrollline would result in type frame making invalid convertion + ---@cast self df_keybindscrollline + end + + self.highlightTexture:Show() + + --if the keybind is a macro, preview the macro text in the edit panel's lua edit box + local bIsEditingKeybind = keyBindFrame:IsEditingKeybindSettings() + + if (not bIsEditingKeybind) then + local keybindScrollData = self.setKeybindButton.widget["keybindScrollData"] + if (keybindScrollData) then + local actionName, iconTexture, actionId, keybindTable, bIsAvailable, sortNumber, actionIdentifier = unpack(keybindScrollData) + ---@cast keybindTable df_keybind + if (actionName ~= "@separator" and keybindTable) then + if (keybindTable.macro and keybindTable.macro ~= "") then + ---@type df_editkeybindframe + editPanel.editMacroEditBox:SetText(keybindTable.macro) + end + + local loadCondition = keybindTable.conditions + local bCanLoad, reason = detailsFramework:PassLoadFilters(loadCondition) + + if (not bCanLoad) then + editPanel.conditionsFailLoadReasonText:SetText("This keybind can't be loaded because it's conditions are not met:\n- " .. (reason or "")) + else + editPanel.conditionsFailLoadReasonText:SetText("") + end + end + end + end + end, + + ---run when the mouse leaves a scroll line + ---@param self df_keybindscrollline + OnLeaveScrollLine = function(self) + if (self.bIsSeparator) then + return + end + + if (not self.keybindScrollLine) then + --when the mouse enters a child frame, the self is the child frame, not the scroll line + self = self:GetParent() ---@diagnostic disable-line getting the parent from df_keybindscrollline would result in type frame making invalid convertion + ---@cast self df_keybindscrollline + end + + self.highlightTexture:Hide() + + --if the keybind is a macro, a preview might be showing in the edit panel's lua edit box, hide it + local keyBindFrame = getMainFrame(self) + local bIsEditingKeybind = keyBindFrame:IsEditingKeybindSettings() + + if (not bIsEditingKeybind) then + local editPanel = keyBindFrame:GetEditPanel() + editPanel.editMacroEditBox:SetText("") + end + end, + + ---@param keybindScroll frame + ---@param index number + CreateKeybindScrollLine = function(keybindScroll, index) --~create + local keyBindFrame = getMainFrame(keybindScroll) + + ---@type df_keybindscrollline + local line = CreateFrame("frame", "$parentLine" .. index, keybindScroll) + line:SetSize(keyBindFrame.options.width - 10, keyBindFrame.options.line_height) + line:SetPoint("topleft", keyBindFrame, "topleft", 1, -22 - (index-1) * keyBindFrame.options.line_height) + line:EnableMouse(true) + line.keybindScrollLine = true + + --detailsFramework:ApplyStandardBackdrop(line, index % 2 == 0) + --line:SetBackdropBorderColor(0, 0, 0, 0) + + line.backgroundTexture = line:CreateTexture("$parentBackgroundTexture", "background") + line.backgroundTexture:SetAllPoints() + + if (index % 2 == 0) then + line.backgroundTexture:SetColorTexture(0, 0, 0, 0.1) + else + line.backgroundTexture:SetColorTexture(0, 0, 0, 0) + end + + line.highlightTexture = line:CreateTexture(nil, "border") + line.highlightTexture:SetAllPoints() + line.highlightTexture:SetColorTexture(1, 1, 1, .1) + line.highlightTexture:Hide() + + line:SetScript("OnEnter", keyBindFrame.OnEnterScrollLine) + line:SetScript("OnLeave", keyBindFrame.OnLeaveScrollLine) + + detailsFramework:Mixin(line, detailsFramework.HeaderFunctions) + + local options_text_template = detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE") + local options_dropdown_template = detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE") + local options_switch_template = detailsFramework:GetTemplate("switch", "OPTIONS_CHECKBOX_TEMPLATE") + local options_slider_template = detailsFramework:GetTemplate("slider", "OPTIONS_SLIDER_TEMPLATE") + local options_button_template = detailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE") + + line.separatorTitleText = line:CreateFontString("$parentSeparatorTitleText", "overlay", "GameFontNormal") + line.separatorTitleText:SetPoint("center", line, "center", 0, 0) + + line.spellIconTexture = line:CreateTexture("$parentIcon", "overlay") + line.spellIconTexture:SetSize(keyBindFrame.options.line_height - 2, keyBindFrame.options.line_height - 2) + + line.actionNameFontString = line:CreateFontString("$parentName", "overlay", "GameFontNormal") + detailsFramework:SetFontColor(line.actionNameFontString, "BLIZZ_OPTIONS_COLOR") + detailsFramework:SetFontSize(line.actionNameFontString, 12) + + ---@type df_button + line.setKeybindButton = detailsFramework:CreateButton(line, function()end, headerTable[3].width, keyBindFrame.options.line_height-6, "", nil, nil, nil, "SetNewKeybindButton", "$parentSetNewKeybindButton", 0, nil, options_text_template) + line.setKeybindButton.textcolor = "white" + line.setKeybindButton.textsize = 10 + line.setKeybindButton:SetHook("OnEnter", keyBindFrame.OnEnterScrollLine) + line.setKeybindButton:SetHook("OnLeave", keyBindFrame.OnLeaveScrollLine) + + detailsFramework:AddRoundedCornersToFrame(line.setKeybindButton, roundedCornerPreset) + + ---@type df_button + line.clearKeybindButton = detailsFramework:CreateButton(line, keyBindFrame.ClearKeybind, 16, keyBindFrame.options.line_height-2, "", nil, nil, nil, "DeleteKeybindButton", "$parentDeleteKeybindButton", 2, nil, options_text_template) + line.clearKeybindButton:SetBackdropBorderColor(0, 0, 0, 0) + line.clearKeybindButton:SetIcon([[Interface\COMMON\CommonIcons]], nil, nil, nil, {0.1264, 0.2514, 0.5048, 0.7548}, nil, nil, 4) + + ---@type df_button + line.editKeybindSettingsButton = detailsFramework:CreateButton(line, keyBindFrame.StartEditingKeybindSettings, 16, keyBindFrame.options.line_height-2, "", nil, nil, nil, "EditKeybindButton", "$parentEditKeybindButton", 2, nil, options_text_template) + line.editKeybindSettingsButton:SetBackdropBorderColor(0, 0, 0, 0) + line.editKeybindSettingsButton:SetIcon([[Interface\BUTTONS\UI-GuildButton-PublicNote-Disabled]]) + + line:AddFrameToHeaderAlignment(line.spellIconTexture) + line:AddFrameToHeaderAlignment(line.actionNameFontString) + line:AddFrameToHeaderAlignment(line.setKeybindButton) + line:AddFrameToHeaderAlignment(line.clearKeybindButton) + line:AddFrameToHeaderAlignment(line.editKeybindSettingsButton) + + line:AlignWithHeader(keyBindFrame.Header, "left") + + line.SetAsSeparator = setAsSeparator + + return line + end, + + ---comment + ---@param self df_keybindframe + CreateKeybindScroll = function(self) --~scroll + local scroll_width = self.options.scroll_width + local scroll_height = self.options.scroll_height + local scroll_lines = self.options.amount_lines + local scroll_line_height = self.options.line_height + + --~header + ---@type df_headerframe + self.Header = DetailsFramework:CreateHeader(self, headerTable, headerOptions) + self.Header:SetPoint("topleft", self, "topleft", 0, 0) + + local onClickCreateMacroButton = function() --~macro + local newMacroName = "New @Macro (" .. math.random(10000, 99999) .. ")" + local actionIdentifier = "macro-" .. newMacroName + + local keybindTable = createNewKeybindTable(newMacroName, "", "/say Hi", actionIdentifier, 136377) + local pressedKeybind = "" + + if (self.options.can_modify_keybind_data) then + --if the options for this frame allows it to change the keybind in the addon savedVariables, then do it + local bKeybindJustCreated = true + self:SaveKeybindToKeybindData(keybindTable, pressedKeybind, bKeybindJustCreated) + end + + self:CallKeybindChangeCallback("modified", keybindTable, pressedKeybind) + + local keybindScroll = self:GetKeybindScroll() + keybindScroll:UpdateScroll() + + --start editing this keybindTable + self:StartEditingKeybindSettings("LeftButton", actionIdentifier, keybindTable) + + --get the keybind editor frame + ---@type df_editkeybindframe + local keybindEditor = self:GetEditPanel() + + local macroEditBox = keybindEditor.editMacroEditBox + macroEditBox:SetText(keybindTable.macro) + macroEditBox:SetFocus() + end + + local createMacroButton = detailsFramework:CreateButton(self.Header, onClickCreateMacroButton, 100, 20, "Create Macro Keybind", nil, nil, nil, "CreateMacroButton", "$parentCreateMacroButton", 0, DARK_BUTTON_TEMPLATE, detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) + createMacroButton:SetPoint("left", self.Header, "left", 0, 0) + createMacroButton:SetFrameLevel(self.Header:GetFrameLevel()+10) + createMacroButton:SetIcon(136377) + + local keybindScroll = detailsFramework:CreateScrollBox(self, "$parentScrollBox", detailsFramework.KeybindMixin.RefreshKeybindScroll, {}, scroll_width, scroll_height, scroll_lines, scroll_line_height) + ---@cast keybindScroll df_keybindscroll + + detailsFramework:ReskinSlider(keybindScroll) + keybindScroll:SetPoint("topleft", self.Header, "bottomleft", 0, -5) + self.keybindScroll = keybindScroll + + keybindScroll:SetBackdropColor(0, 0, 0, 0) + keybindScroll:SetBackdropBorderColor(0, 0, 0, 0) + keybindScroll.__background:SetAlpha(0) + + for i = 1, scroll_lines do + keybindScroll:CreateLine(self.CreateKeybindScrollLine) + end + + --scroll data constructor + function keybindScroll.UpdateScroll() --~update + --keybind data from saved variables + ---@type df_keybind[] + local data = self:GetKeybindData() + + --pre build keybind data to be used by the scroll data constructor + ---@type table + local keybindDataParsed = {} + + ---@type df_keybind[] + local allKeybindMacros = {} + + --iterage amoung keybinds already set by the user + --and fill the tables 'keybindDataParsed' where the key is actionId and the value is the keybind data + --also fill the table 'allKeybindMacros' with all macros, this is an array with keybind data + for i = 1, #data do + ---@type df_keybind + local keybindData = data[i] + + local actionId = keybindData.action --the actionId can be "macro" for macros + local keybind = keybindData.keybind --the keybind to active the action + local macro = keybindData.macro --macro here is the macro text + local name = keybindData.name --for macros it shows the macro name where the spellName is for instance + local icon = keybindData.icon --for macros the user can set an icon + local conditions = keybindData.conditions --allows the user to set conditions for the keybind to be active + + local bIsSpell = actionId and type(actionId) == "number" + if (bIsSpell) then + local spellId = actionId + keybindDataParsed[spellId] = keybindData + + elseif (defaultMouseKeybindsKV[actionId]) then + keybindDataParsed[actionId] = keybindData --"target" "focus" "togglemenu" + + end + + if (type(actionId) == "string" and self:IsKeybindActionMacro(actionId)) then + table.insert(allKeybindMacros, keybindData) + end + end + + ---@type keybind_scroll_data[] + local scrollData = {} + + ---@type keybind_scroll_data[] store spells that are not available, they are added after the spells available + local spellsNotAvailable = {} + + ---@type keybind_scroll_data[] store macros that are not available, they are added after the spells available + local macrosNotAvailable = {} + + table.insert(scrollData, {"@separator", "Regualar Actions", "", "", false, -1}) + + if (self.options.show_unitcontrols) then + for i, mouseActionKeyInfo in ipairs(defaultMouseKeybinds) do + local mouseActionId = mouseActionKeyInfo.action + local mouseDefaultKeybind = mouseActionKeyInfo.keybind + local mouseIcon = mouseActionKeyInfo.icon + local mouseActionName = mouseActionKeyInfo.name + + local keybindData = keybindDataParsed[mouseActionId] + local actionIdentifier = "system-" .. mouseActionId + + local thisScrollData = {keybindData and keybindData.name or mouseActionName, keybindData and keybindData.icon or mouseIcon, mouseActionId, keybindData or false, true, 0, actionIdentifier} + table.insert(scrollData, 1+i, thisScrollData) + end + end + + table.insert(scrollData, {"@separator", "Macros", "", "", false, 1}) + + if (self.options.show_macros) then + --sort the table alphabetically + table.sort(allKeybindMacros, function(t1, t2) return t1.name < t2.name end) + + for i, keybindData in ipairs(allKeybindMacros) do + local macroName = keybindData.name + local macroIcon = keybindData.icon + local macroText = keybindData.macro + local actionId = keybindData.action + local conditions = keybindData.conditions + + local bCanLoad = detailsFramework:PassLoadFilters(conditions) + local sortScore = 2 + + local actionIdentifier = actionId + + ---@type keybind_scroll_data + local thisScrollData = {macroName, macroIcon, actionId, keybindData, bCanLoad, sortScore, actionIdentifier} + if (bCanLoad) then + table.insert(scrollData, thisScrollData) + else + table.insert(macrosNotAvailable, thisScrollData) + end + end + end + + table.insert(scrollData, {"@separator", "Spells", "", "", false, 3}) + + local indexToAddNotAvailableMacros = #scrollData + 1 + + if (self.options.show_spells) then + --the a list of all spells + local allPlayerSpells = detailsFramework:GetAvailableSpells() + --bIsAvailable is a boolean that tells if the spell is from the spec the player is currently using (spells grayed out on the spellbook would be false here) + for spellId, bIsAvailable in pairs(allPlayerSpells) do + local spellName, _, spellIcon = GetSpellInfo(spellId) + if (spellName) then + ---@type df_keybind|nil + local keybindData = keybindDataParsed[spellId] --could be nil if doesn't exists + + --show spells with keybinds at the top of the list, then show spells that are available, then show spells that are not available + --always sub sorting by the spell name + local sortScore = getSpellSortOrder(keybindData, spellName, bIsAvailable) + local actionId = spellId + + local actionIdentifier = "spell-" .. actionId + + ---@type keybind_scroll_data + local thisScrollData = {keybindData and keybindData.name or spellName, keybindData and keybindData.icon or spellIcon, actionId, keybindData or false, bIsAvailable, sortScore, actionIdentifier} + + if (not bIsAvailable) then + spellsNotAvailable[#spellsNotAvailable+1] = thisScrollData + else + scrollData[#scrollData+1] = thisScrollData + end + end + end + + table.sort(scrollData, function(a, b) return a[6] < b[6] end) + table.sort(spellsNotAvailable, function(a, b) return a[6] < b[6] end) + end + + if (#macrosNotAvailable > 0) then + table.insert(scrollData, {"@separator", "Macros Not Available", "", "", false, 1}) + + for i = 1, #macrosNotAvailable do + local thisScrollData = macrosNotAvailable[i] + table.insert(scrollData, thisScrollData) + end + end + + if (#spellsNotAvailable > 0) then + table.insert(scrollData, {"@separator", "Spells Not Available", "", "", false, 3}) + + for i = 1, #spellsNotAvailable do + local thisScrollData = spellsNotAvailable[i] + table.insert(scrollData, thisScrollData) + end + end + + keybindScroll:SetData(scrollData) + keybindScroll:Refresh() + end + end, + + ---return the keybind data + ---@param self df_keybindframe + ---@return df_keybind[] + GetKeybindData = function(self) + return self.data + end, + + ---set the keybind data from a profile + ---data consists in a table where the actionId (any) is the key and the value is the keybind (string) + ---@param self df_keybindframe + ---@param newData df_keybind[] + SetKeybindData = function(self, newData) + self.data = newData + local keybindScroll = self:GetKeybindScroll() + keybindScroll:UpdateScroll() + end, + + ---set the callback function to be called when the player set or clear a keybind + ---@param self df_keybindframe + ---@param callback function + SetKeybindCallback = function(self, callback) + self.callback = callback + local keybindScroll = self:GetKeybindScroll() + keybindScroll:UpdateScroll() + end, + + ---@param self df_keybindframe + ---@return function + GetKeybindCallback = function(self) + return self.callback + end, + + ---@param self df_keybindframe + ---@param type string "modified", "removed", "conditions", "name" + ---@param keybindTable df_keybind? + ---@param keybindPressed string? + ---@param removedIndex number? + ---@param macroText string? + CallKeybindChangeCallback = function(self, type, keybindTable, keybindPressed, removedIndex, macroText) + local callbackFunc = self:GetKeybindCallback() + if (callbackFunc) then + detailsFramework:Dispatch(callbackFunc, self, type, keybindTable, keybindPressed, removedIndex, macroText) + end + end, + + ---@param self df_keybindframe + ---@param actionId any + IsKeybindActionMacro = function(self, actionId) + if (type(actionId) == "string") then + return actionId:match("^macro%-") + end + end, + + ---on press enter on the edit frame name editbox + ---@param self df_keybindframe + ---@param newName string + OnKeybindNameChange = function(self, newName) + local editFrame = self:GetEditPanel() + local keybindTable = editFrame.keybindTable + + local actionId = keybindTable.action + ---@cast actionId string + + keybindTable.name = newName + + if (self:IsKeybindActionMacro(actionId)) then + keybindTable.action = "macro-" .. newName + end + + self:CallKeybindChangeCallback("name", keybindTable) + + local keybindScroll = self:GetKeybindScroll() + keybindScroll:UpdateScroll() + end, + + ---@param self df_keybindframe + ---@param macroText string + OnKeybindMacroChange = function(self, macroText) + ---@type df_keybindframe + local keyBindFrame = getMainFrame(self) + + local editFrame = keyBindFrame:GetEditPanel() + local keybindTable = editFrame.keybindTable + + if (keyBindFrame.options.can_modify_keybind_data) then + keybindTable.macro = macroText + end + + keyBindFrame:CallKeybindChangeCallback("macro", keybindTable, nil, nil, macroText) + end, + + OnKeybindIconChange = function(self, texture) + local editFrame = self:GetEditPanel() + local keybindTable = editFrame.keybindTable + keybindTable.icon = texture + self:CallKeybindChangeCallback("icon", keybindTable) + end, + + ---return true if the user is editing a keybind + ---@param self df_keybindframe + ---@return boolean bIsEditing + ---@return string actionIdentifier + ---@return df_keybind keybindTable + IsEditingKeybindSettings = function(self) + local editFrame = self:GetEditPanel() + return editFrame.bIsEditing, editFrame.actionIdentifier, editFrame.keybindTable + end, + + ---start editing the keybind settings + ---@param self frame + ---@param button string + ---@param actionIdentifier string + ---@param keybindTable df_keybind + StartEditingKeybindSettings = function(self, button, actionIdentifier, keybindTable) + ---@type df_keybindframe + local keyBindFrame = getMainFrame(self) + + local bIsListening = keyBindFrame:GetListeningState() + if (bIsListening) then + return + end + + local editFrame = keyBindFrame:GetEditPanel() + editFrame:Enable() + + editFrame.nameEditBox:SetText(keybindTable.name) + editFrame.iconPickerButton:SetIcon(keybindTable.icon) + + local actionId = keybindTable.action + ---@cast actionId string + if (keyBindFrame:IsKeybindActionMacro(actionId)) then + editFrame.editMacroEditBox:SetText(keybindTable.macro) + editFrame.deleteMacroButton:Enable() + else + editFrame.editMacroEditBox:Disable() + editFrame.deleteMacroButton:Disable() + end + + editFrame.actionIdentifier = actionIdentifier + editFrame.keybindTable = keybindTable + editFrame.bIsEditing = true + end, + + ---disable and clear all entries in the edit frame + ---@param self df_keybindframe + StopEditingKeybindSettings = function(self) + local editFrame = self:GetEditPanel() + editFrame.bIsEditing = false + editFrame.actionIdentifier = nil + editFrame.keybindTable = nil + editFrame:Disable() + end, + + ---return the editing keybind frame + ---@param self df_keybindframe + ---@return df_editkeybindframe + GetEditPanel = function(self) + return self.editKeybindFrame + end, + + ---@param self df_keybindframe + CreateEditPanel = function(self) --~edit + local options_text_template = detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE") + local options_dropdown_template = detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE") + local options_switch_template = detailsFramework:GetTemplate("switch", "OPTIONS_CHECKBOX_TEMPLATE") + local options_slider_template = detailsFramework:GetTemplate("slider", "OPTIONS_SLIDER_TEMPLATE") + local options_button_template = detailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE") + + ---@type df_editkeybindframe + local editFrame = CreateFrame("frame", "$parentEditPanel", self, "BackdropTemplate") + editFrame:SetSize(self.options.edit_width, self.options.edit_height) + editFrame:SetPoint("topleft", self, "topright", 28, 0) --give space for the scrollbar + self.editKeybindFrame = editFrame + + --name + local nameText = editFrame:CreateFontString("$parentNameText", "overlay", "GameFontNormal") + nameText:SetPoint("topleft", editFrame, "topleft", 10, -10) + nameText:SetText("Name:") + detailsFramework:SetFontColor(nameText, "BLIZZ_OPTIONS_COLOR") + + local nameEditBoxCallback = function(param1, param2, text) + --print("name change", param1, param2, text) + --self:OnKeybindNameChange(text) + end + local nameEditBox = detailsFramework:CreateTextEntry(editFrame, nameEditBoxCallback, 200, 20, "nameEditBox", "$parentNameEditBox", nil, options_dropdown_template) + nameEditBox:SetPoint("topleft", nameText, "bottomleft", 0, -5) + nameEditBox:SetBackdropColor(.1, .1, .1, .834) + nameEditBox:SetJustifyH("left") + nameEditBox:SetTextInsets(5, 3, 0, 0) + + --icon + local iconText = editFrame:CreateFontString("$parentIconText", "overlay", "GameFontNormal") + iconText:SetPoint("topleft", nameEditBox.widget, "bottomleft", 0, -10) + iconText:SetText("Icon:") + detailsFramework:SetFontColor(iconText, "BLIZZ_OPTIONS_COLOR") + + local iconPickerButtonCallback = function(texture) + editFrame.iconPickerButton:SetIcon(texture) + --self:OnKeybindIconChange(texture) + end + + local iconPickerButton = detailsFramework:CreateButton(editFrame, function() detailsFramework:IconPick(iconPickerButtonCallback, true) end, 20, 20, "", nil, nil, nil, "iconPickerButton", "$parentIconPickerButton", 0, DARK_BUTTON_TEMPLATE, detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) + iconPickerButton:SetPoint("topleft", iconText, "bottomleft", 0, -5) + iconPickerButton:SetIcon([[]], nil, nil, nil, {0.1264, 0.2514, 0.5048, 0.7548}, nil, nil, 4) + iconPickerButton.tooltip = "pick an icon" + + --macro + local editMacroText = editFrame:CreateFontString("$parentEditMacroText", "overlay", "GameFontNormal") + editMacroText:SetPoint("topleft", iconPickerButton.widget, "bottomleft", 0, -10) + editMacroText:SetText("Macro:") + detailsFramework:SetFontColor(editMacroText, "BLIZZ_OPTIONS_COLOR") + + ---@type df_luaeditor + local editMacroEditBox = detailsFramework:NewSpecialLuaEditorEntry(editFrame, self.options.edit_width-35, 200, "editMacroEditBox", "$parentEditMacroEditBox", true) + editMacroEditBox:SetBackdrop ({edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}) + editMacroEditBox:SetBackdropBorderColor(0, 0, 0, 1) + editMacroEditBox:SetBackdropColor(.1, .1, .1, .5) + detailsFramework:ReskinSlider(editMacroEditBox.scroll) + editMacroEditBox:SetPoint("topleft", editMacroText, "bottomleft", 0, -5) + + editMacroEditBox["Center"]:SetColorTexture(.1, .1, .1, .834) + + local saveButtonCallback = function() + local bIsEditing, actionIdentifier, keybindTable = self:IsEditingKeybindSettings() + if (bIsEditing and keybindTable) then + local keybindName = nameEditBox:GetText() + local iconTexture = editFrame.iconPickerButton.icon + local keybindTexture = iconTexture:GetTexture() + local keybindMacroText = editMacroEditBox:GetText() + + --check if the macro has default name and icon + if (keybindName:find("@Macro") and iconTexture:GetTexture() == 136377) then + for macro in keybindMacroText:gmatch("([^%s]+)") do + local spellName, _, spellIcon = GetSpellInfo(macro) + if (spellName) then + keybindName = spellName + keybindTexture = spellIcon + end + end + end + + self:OnKeybindNameChange(keybindName) + self:OnKeybindIconChange(keybindTexture) + + local actionId = keybindTable.action + ---@cast actionId string + + if (self:IsKeybindActionMacro(actionId)) then + self:OnKeybindMacroChange(keybindMacroText) + end + end + + self:StopEditingKeybindSettings() + + local keybindScroll = self:GetKeybindScroll() + keybindScroll:UpdateScroll() + end + + --save button + local saveButton = detailsFramework:CreateButton(editFrame, saveButtonCallback, 120, 20, "Save", nil, nil, nil, "saveButton", "$parentSaveButton", 0, DARK_BUTTON_TEMPLATE, detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) + saveButton:SetPoint("topleft", editMacroEditBox, "bottomleft", 0, -10) + saveButton:SetIcon([[Interface\BUTTONS\UI-CheckBox-Check]]) + + local cancelButtonCallback = function() + self:StopEditingKeybindSettings() + end + + --cancel button + local cancelButton = detailsFramework:CreateButton(editFrame, cancelButtonCallback, 120, 20, "Cancel", nil, nil, nil, "cancelButton", "$parentCancelButton", 0, DARK_BUTTON_TEMPLATE, detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) + cancelButton:SetPoint("left", saveButton, "right", 10, 0) + cancelButton:SetIcon([[Interface\BUTTONS\UI-GROUPLOOT-PASS-DOWN]]) + + --conditions + local conditionsText = editFrame:CreateFontString("$parentConditionsText", "overlay", "GameFontNormal") + conditionsText:SetPoint("topleft", saveButton.widget, "bottomleft", 0, -40) + conditionsText:SetText("Can Load Keybind?") + detailsFramework:SetFontColor(conditionsText, "BLIZZ_OPTIONS_COLOR") + + local onLoadConditionsChange = function() + --no parameters is passed as the modifications are done directly on the keybindTable.conditions + --trigger a callback to inform the addon about the change + self:CallKeybindChangeCallback("conditions", editFrame.keybindTable) + end + + local openConditionsPanel = function() + local conditionsSettings = editFrame.keybindTable.conditions + detailsFramework:OpenLoadConditionsPanel(conditionsSettings, onLoadConditionsChange, {title = "Keybind Load Conditions", name = editFrame.keybindTable.name}) + end + + local conditionsButton = detailsFramework:CreateButton(editFrame, openConditionsPanel, 160, 20, "Edit Load Conditions", nil, nil, [[]], "conditionsButton", "$parentConditionsButton", 0, DARK_BUTTON_TEMPLATE, detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) + conditionsButton:SetPoint("topleft", conditionsText, "bottomleft", 0, -5) + + local conditionsFailLoadReasonText = editFrame:CreateFontString("$parentConditionsFailLoadText", "overlay", "GameFontNormal") + conditionsFailLoadReasonText:SetPoint("topleft", conditionsButton.widget, "bottomleft", 0, -20) + conditionsFailLoadReasonText:SetText("") + conditionsFailLoadReasonText:SetJustifyH("left") + detailsFramework:SetFontColor(conditionsFailLoadReasonText, "firebrick") + editFrame.conditionsFailLoadReasonText = conditionsFailLoadReasonText + + --create a button to delete a macro keybind + local deleteMacroText = editFrame:CreateFontString("$parentDeleteMacroText", "overlay", "GameFontNormal") + deleteMacroText:SetPoint("topleft", saveButton.widget, "bottomleft", 180, -40) + deleteMacroText:SetText("Delete Macro") + detailsFramework:SetFontColor(deleteMacroText, "BLIZZ_OPTIONS_COLOR") + + local onClickDeleteMacroButton = function() + self:DeleteMacro() + end + + local deleteMacroButton = detailsFramework:CreateButton(editFrame, onClickDeleteMacroButton, 160, 20, "Delete This Macro", nil, nil, [[]], "deleteMacroButton", "$parentDeleteMacroButton", 0, DARK_BUTTON_TEMPLATE, detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")) + deleteMacroButton:SetPoint("topleft", deleteMacroText, "bottomleft", 0, -5) + + --methods + function editFrame:Disable() + nameEditBox:SetText("") + nameEditBox:Disable() + editMacroEditBox:SetText("") + editMacroEditBox:Disable() + iconPickerButton:Disable() + conditionsButton:Disable() + deleteMacroButton:Disable() + saveButton:Disable() + cancelButton:Disable() + end + + function editFrame:Enable() + nameEditBox:Enable() + iconPickerButton:Enable() + conditionsButton:Enable() + deleteMacroButton:Enable() + editMacroEditBox:Enable() + saveButton:Enable() + cancelButton:Enable() + end + + editFrame:Disable() + end, +} + +---@param parent frame +---@param name string? +---@param options table? +---@param setKeybindCallback function? +---@param keybindData table? +function detailsFramework:CreateKeybindFrame(parent, name, options, setKeybindCallback, keybindData) + ---@type df_keybindframe + local keyBindFrame = CreateFrame("frame", name, parent, "BackdropTemplate") + keyBindFrame.bIsKeybindFrame = true + + detailsFramework:Mixin(keyBindFrame, detailsFramework.OptionsFunctions) + detailsFramework:Mixin(keyBindFrame, detailsFramework.KeybindMixin) + + options = options or {} + keyBindFrame:BuildOptionsTable(default_options, options) + + if (keyBindFrame.options.width ~= default_options.width or keyBindFrame.options.height ~= default_options.height) then + local lineHeight = keyBindFrame.options.line_height + keyBindFrame.options.amount_lines = math.floor((keyBindFrame.options.height - 20) / lineHeight) + keyBindFrame.options.scroll_height = keyBindFrame.options.height - 20 + keyBindFrame.options.scroll_width = keyBindFrame.options.width - 10 + end + + if (keyBindFrame.options.edit_height == 0) then + keyBindFrame.options.edit_height = keyBindFrame.options.height + end + + keyBindFrame:SetSize(keyBindFrame.options.width, keyBindFrame.options.height) + + keyBindFrame:SetScript("OnHide", function() + if (keyBindFrame:IsListening()) then + keyBindFrame:SetListeningState(false) + local keybindListener = keyBindFrame:GetKeybindListener() + keybindListener:SetScript("OnKeyDown", nil) + end + + local bIsEditingKeybind = keyBindFrame:IsEditingKeybindSettings() + if (bIsEditingKeybind) then + keyBindFrame:StopEditingKeybindSettings() + end + + keyBindFrame:SetClearButtonsEnabled(true) + keyBindFrame:SetEditButtonsEnabled(true) + end) + + keyBindFrame:SetScript("OnShow", function() + local keybindScroll = keyBindFrame:GetKeybindScroll() + keybindScroll:UpdateScroll() + end) + + keyBindFrame:CreateKeybindScroll() + keyBindFrame:CreateKeybindListener() + keyBindFrame:CreateEditPanel() + + keyBindFrame:SetKeybindData(keybindData or {}) + + if (setKeybindCallback) then + keyBindFrame:SetKeybindCallback(setKeybindCallback) + end + + return keyBindFrame +end diff --git a/libs/DF/label.lua b/libs/DF/label.lua index a462993c..8514562b 100644 --- a/libs/DF/label.lua +++ b/libs/DF/label.lua @@ -110,7 +110,13 @@ detailsFramework:Mixin(LabelMetaFunctions, detailsFramework.ScriptHookMixin) --text local smember_text = function(object, value) - return object.label:SetText(value) + --check if this is a loc table + if (detailsFramework.Language.IsLocTable(value)) then + local locTable = value + detailsFramework.Language.RegisterObjectWithLocTable(object.widget or object, locTable) + else + return object.label:SetText(value) + end end --text color @@ -235,6 +241,10 @@ detailsFramework:Mixin(LabelMetaFunctions, detailsFramework.ScriptHookMixin) return self.label:SetTextColor(red, green, blue, alpha) end + function LabelMetaFunctions:SetText(text) + return smember_text(self, text) + end + ------------------------------------------------------------------------------------------------------------ --template @@ -256,7 +266,7 @@ detailsFramework:Mixin(LabelMetaFunctions, detailsFramework.ScriptHookMixin) ------------------------------------------------------------------------------------------------------------ --object constructor ----@class df_label +---@class df_label: fontstring ---@field widget fontstring widget and label points to the same fontstring ---@field label fontstring widget and label points to the same fontstring ---@field align justifyh @@ -274,6 +284,7 @@ detailsFramework:Mixin(LabelMetaFunctions, detailsFramework.ScriptHookMixin) ---@field shadow fontflags ---@field outline fontflags ---@field rotation number +---@field SetPoint fun(self: df_label, point: any, relativeTo: any, relativePoint: any, x: any, y: any) set the label position ---@field SetTemplate fun(self: df_label, template: table) set the fontstring visual by a template ---@field SetTextColor fun(self: df_label, red: any, green: number|nil, blue: number|nil, alpha: number|nil) set the button text color ---@field SetTextTruncated fun(self: df_label, text: string, maxWidth: width) diff --git a/libs/DF/languages.lua b/libs/DF/languages.lua index 6776b981..55e93984 100644 --- a/libs/DF/languages.lua +++ b/libs/DF/languages.lua @@ -1628,6 +1628,7 @@ function DF.Language.CreateLanguageSelector(addonId, parent, callback, selectedL local languageLabel = DF:CreateLabel(parent, _G.LANGUAGE .. ":", 10, "silver") languageLabel:SetPoint("right", languageSelector, "left", -3, 0) + languageSelector.languageLabel = languageLabel return languageSelector end diff --git a/libs/DF/load.xml b/libs/DF/load.xml index b7674865..c0505fa8 100644 --- a/libs/DF/load.xml +++ b/libs/DF/load.xml @@ -23,7 +23,7 @@