Hekili

Untitled

Aug 15th, 2024
150
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 128.01 KB | None | 0 0
  1. -- UI.lua
  2. -- Dynamic UI Elements
  3.  
  4. local addon, ns = ...
  5. local Hekili = _G[addon]
  6.  
  7. local class = Hekili.Class
  8. local state = Hekili.State
  9.  
  10. local FindUnitBuffByID, FindUnitDebuffByID = ns.FindUnitBuffByID, ns.FindUnitDebuffByID
  11.  
  12. -- Atlas/Textures
  13. local AddTexString, GetTexString, AtlasToString, GetAtlasFile, GetAtlasCoords = ns.AddTexString, ns.GetTexString, ns.AtlasToString, ns.GetAtlasFile, ns.GetAtlasCoords
  14.  
  15. local frameStratas = ns.FrameStratas
  16. local getInverseDirection = ns.getInverseDirection
  17. local multiUnpack = ns.multiUnpack
  18. local orderedPairs = ns.orderedPairs
  19. local round = ns.round
  20.  
  21. local IsCurrentItem = C_Item.IsCurrentItem
  22. local IsUsableItem = C_Item.IsUsableItem
  23. local IsCurrentSpell = C_Spell.IsCurrentSpell
  24. local GetItemCooldown = C_Item.GetItemCooldown
  25. local GetSpellTexture = C_Spell.GetSpellTexture
  26. local IsUsableSpell = C_Spell.IsSpellUsable
  27.  
  28. local GetSpellCooldown = function(spellID)
  29.     local spellCooldownInfo = C_Spell.GetSpellCooldown(spellID)
  30.     if spellCooldownInfo then
  31.         return spellCooldownInfo.startTime, spellCooldownInfo.duration, spellCooldownInfo.isEnabled, spellCooldownInfo.modRate
  32.     end
  33.     return 0, 0, false, 0
  34. end
  35.  
  36. local format, insert = string.format, table.insert
  37.  
  38. local HasVehicleActionBar, HasOverrideActionBar, IsInPetBattle, UnitHasVehicleUI, UnitOnTaxi = HasVehicleActionBar, HasOverrideActionBar, C_PetBattles.IsInBattle, UnitHasVehicleUI, UnitOnTaxi
  39. local Tooltip = ns.Tooltip
  40.  
  41. local Masque, MasqueGroup
  42. local _
  43.  
  44.  
  45. function Hekili:GetScale()
  46.     return PixelUtil.GetNearestPixelSize( 1, PixelUtil.GetPixelToUIUnitFactor(), 1 )
  47.     --[[ local monitorIndex = (tonumber(GetCVar("gxMonitor")) or 0) + 1
  48.     local resolutions = {GetScreenResolutions()}
  49.     local resolution = resolutions[GetCurrentResolution()] or GetCVar("gxWindowedResolution")
  50.  
  51.     return (GetCVar("UseUIScale") == "1" and (GetScreenHeight() / resolution:match("%d+x(%d+)")) or 1) ]]
  52. end
  53.  
  54.  
  55. local movementData = {}
  56.  
  57. local function startScreenMovement(frame)
  58.     movementData.origX, movementData.origY = select( 4, frame:GetPoint() )
  59.     frame:StartMoving()
  60.     movementData.fromX, movementData.fromY = select( 4, frame:GetPoint() )
  61.     frame.Moving = true
  62. end
  63.  
  64. local function stopScreenMovement(frame)
  65.     local resolution = C_VideoOptions.GetCurrentGameWindowSize()
  66.     local scrW, scrH = resolution.x, resolution.y
  67.  
  68.     local scale, pScale = Hekili:GetScale(), UIParent:GetScale()
  69.  
  70.     scrW = scrW / ( scale * pScale )
  71.     scrH = scrH / ( scale * pScale )
  72.  
  73.     local limitX = (scrW - frame:GetWidth() ) / 2
  74.     local limitY = (scrH - frame:GetHeight()) / 2
  75.  
  76.     movementData.toX, movementData.toY = select( 4, frame:GetPoint() )
  77.     frame:StopMovingOrSizing()
  78.     frame.Moving = false
  79.     frame:ClearAllPoints()
  80.     frame:SetPoint( "CENTER", nil, "CENTER",
  81.         max(-limitX, min(limitX, movementData.origX + (movementData.toX - movementData.fromX))),
  82.         max(-limitY, min(limitY, movementData.origY + (movementData.toY - movementData.fromY))) )
  83.     Hekili:SaveCoordinates()
  84. end
  85.  
  86. local function Mover_OnMouseUp(self, btn)
  87.     local obj = self.moveObj or self
  88.  
  89.     if (btn == "LeftButton" and obj.Moving) then
  90.         stopScreenMovement(obj)
  91.         Hekili:SaveCoordinates()
  92.     elseif btn == "RightButton" then
  93.         if obj:GetName() == "HekiliNotification" then
  94.             LibStub( "AceConfigDialog-3.0" ):SelectGroup( "Hekili", "displays", "nPanel" )
  95.             return
  96.         elseif obj and obj.id then
  97.             LibStub( "AceConfigDialog-3.0" ):SelectGroup( "Hekili", "displays", obj.id )
  98.             return
  99.         end
  100.     end
  101. end
  102.  
  103. local function Mover_OnMouseDown( self, btn )
  104.     local obj = self.moveObj or self
  105.  
  106.     if Hekili.Config and btn == "LeftButton" and not obj.Moving then
  107.         startScreenMovement(obj)
  108.     end
  109. end
  110.  
  111. local function Button_OnMouseUp( self, btn )
  112.     local display = self.display
  113.     local mover = _G[ "HekiliDisplay" .. display ]
  114.  
  115.     if (btn == "LeftButton" and mover.Moving) then
  116.         stopScreenMovement(mover)
  117.  
  118.     elseif (btn == "RightButton") then
  119.         if mover.Moving then
  120.             stopScreenMovement(mover)
  121.         end
  122.         local mouseInteract = Hekili.Pause or Hekili.Config
  123.         for i = 1, #ns.UI.Buttons do
  124.             for j = 1, #ns.UI.Buttons[i] do
  125.                 ns.UI.Buttons[i][j]:EnableMouse(mouseInteract)
  126.             end
  127.         end
  128.         ns.UI.Notification:EnableMouse( Hekili.Config )
  129.         -- Hekili:SetOption( { "locked" }, true )
  130.         GameTooltip:Hide()
  131.  
  132.     end
  133.  
  134.     Hekili:SaveCoordinates()
  135. end
  136.  
  137. local function Button_OnMouseDown(self, btn)
  138.     local display = self.display
  139.     local mover = _G[ "HekiliDisplay" .. display ]
  140.  
  141.     if Hekili.Config and btn == "LeftButton" and not mover.Moving then
  142.         startScreenMovement(mover)
  143.     end
  144. end
  145.  
  146.  
  147. function ns.StartConfiguration( external )
  148.     Hekili.Config = true
  149.  
  150.     local scaleFactor = Hekili:GetScale()
  151.     local ccolor = RAID_CLASS_COLORS[select(2, UnitClass("player"))]
  152.  
  153.     -- Notification Panel
  154.     ns.UI.Notification.Mover = ns.UI.Notification.Mover or CreateFrame( "Frame", "HekiliNotificationMover", ns.UI.Notification, "BackdropTemplate" )
  155.     ns.UI.Notification.Mover:SetAllPoints(HekiliNotification)
  156.     ns.UI.Notification.Mover:SetBackdrop( {
  157.         bgFile = "Interface/Buttons/WHITE8X8",
  158.         edgeFile = "Interface/Buttons/WHITE8X8",
  159.         tile = false,
  160.         tileSize = 0,
  161.         edgeSize = 1,
  162.         insets = { left = 0, right = 0, top = 0, bottom = 0 }
  163.     } )
  164.  
  165.     ns.UI.Notification.Mover:SetBackdropColor( 0, 0, 0, .8 )
  166.     ns.UI.Notification.Mover:SetBackdropBorderColor( ccolor.r, ccolor.g, ccolor.b, 1 )
  167.     ns.UI.Notification.Mover:Show()
  168.  
  169.     local f = ns.UI.Notification.Mover
  170.  
  171.     if not f.Header then
  172.         f.Header = f:CreateFontString( "HekiliNotificationHeader", "OVERLAY", "GameFontNormal" )
  173.         local path = f.Header:GetFont()
  174.         f.Header:SetFont( path, 18, "OUTLINE" )
  175.     end
  176.     f.Header:SetAllPoints( HekiliNotificationMover )
  177.     f.Header:SetText( "Notifications" )
  178.     f.Header:SetJustifyH( "CENTER" )
  179.     f.Header:Show()
  180.  
  181.     if HekiliNotificationMover:GetFrameLevel() > HekiliNotification:GetFrameLevel() then
  182.         local orig = HekiliNotificationMover:GetFrameLevel()
  183.         HekiliNotification:SetFrameLevel(orig)
  184.         HekiliNotificationMover:SetFrameLevel(orig-1)
  185.     end
  186.  
  187.     ns.UI.Notification:EnableMouse( true )
  188.     ns.UI.Notification:SetMovable( true )
  189.  
  190.     HekiliNotification:SetScript( "OnMouseDown", Mover_OnMouseDown )
  191.     HekiliNotification:SetScript( "OnMouseUp", Mover_OnMouseUp )
  192.     HekiliNotification:SetScript( "OnEnter", function( self )
  193.         local H = Hekili
  194.  
  195.         if H.Config then
  196.             Tooltip:SetOwner( self, "ANCHOR_TOPRIGHT" )
  197.  
  198.             Tooltip:SetText( "Hekili: Notifications" )
  199.             Tooltip:AddLine( "Left-click and hold to move.", 1, 1, 1 )
  200.             Tooltip:AddLine( "Right-click to open Notification panel settings.", 1, 1, 1 )
  201.             Tooltip:Show()
  202.         end
  203.     end )
  204.     HekiliNotification:SetScript( "OnLeave", function(self)
  205.         Tooltip:Hide()
  206.     end )
  207.  
  208.     Hekili:ProfileFrame( "NotificationFrame", HekiliNotification )
  209.  
  210.     for i, v in pairs( ns.UI.Displays ) do
  211.         if v.Backdrop then
  212.             v.Backdrop:Hide()
  213.         end
  214.  
  215.         if v.Header then
  216.             v.Header:Hide()
  217.         end
  218.  
  219.         if ns.UI.Buttons[ i ][ 1 ] and Hekili.DB.profile.displays[ i ] then
  220.             -- if not Hekili:IsDisplayActive( i ) then v:Show() end
  221.  
  222.             v.Backdrop = v.Backdrop or CreateFrame( "Frame", v:GetName().. "_Backdrop", UIParent, "BackdropTemplate" )
  223.             v.Backdrop:ClearAllPoints()
  224.  
  225.             if not v:IsAnchoringRestricted() then
  226.                 v:EnableMouse( true )
  227.                 v:SetMovable( true )
  228.  
  229.                 for id, btn in ipairs( ns.UI.Buttons[ i ] ) do
  230.                     btn:EnableMouse( false )
  231.                 end
  232.  
  233.                 local left, right, top, bottom = v:GetPerimeterButtons()
  234.                 if left and right and top and bottom then
  235.                     v.Backdrop:SetPoint( "LEFT", left, "LEFT", -2, 0 )
  236.                     v.Backdrop:SetPoint( "RIGHT", right, "RIGHT", 2, 0 )
  237.                     v.Backdrop:SetPoint( "TOP", top, "TOP", 0, 2 )
  238.                     v.Backdrop:SetPoint( "BOTTOM", bottom, "BOTTOM", 0, -2 )
  239.                 else
  240.                     v.Backdrop:SetWidth( v:GetWidth() + 2 )
  241.                     v.Backdrop:SetHeight( v:GetHeight() + 2 )
  242.                     v.Backdrop:SetPoint( "CENTER", v, "CENTER" )
  243.                 end
  244.             end
  245.  
  246.             v.Backdrop:SetFrameStrata( v:GetFrameStrata() )
  247.             v.Backdrop:SetFrameLevel( v:GetFrameLevel() + 1 )
  248.  
  249.             v.Backdrop.moveObj = v
  250.  
  251.             v.Backdrop:SetBackdrop( {
  252.                 bgFile = "Interface/Buttons/WHITE8X8",
  253.                 edgeFile = "Interface/Buttons/WHITE8X8",
  254.                 tile = false,
  255.                 tileSize = 0,
  256.                 edgeSize = 1,
  257.                 insets = { left = 0, right = 0, top = 0, bottom = 0 }
  258.             } )
  259.  
  260.             local ccolor = RAID_CLASS_COLORS[ select(2, UnitClass("player")) ]
  261.  
  262.             if Hekili:IsDisplayActive( v.id, true ) then
  263.                 v.Backdrop:SetBackdropBorderColor( ccolor.r, ccolor.g, ccolor.b, 1 )
  264.             else
  265.                 v.Backdrop:SetBackdropBorderColor( 0.5, 0.5, 0.5, 0.5 )
  266.             end
  267.             v.Backdrop:SetBackdropColor( 0, 0, 0, 0.8 )
  268.             v.Backdrop:Show()
  269.  
  270.             v.Backdrop:SetScript( "OnMouseDown", Mover_OnMouseDown )
  271.             v.Backdrop:SetScript( "OnMouseUp", Mover_OnMouseUp )
  272.             v.Backdrop:SetScript( "OnEnter", function( self )
  273.                 local H = Hekili
  274.  
  275.                 if H.Config then
  276.                     Tooltip:SetOwner( self, "ANCHOR_TOPRIGHT" )
  277.  
  278.                     Tooltip:SetText( "Hekili: " .. i )
  279.                     Tooltip:AddLine( "Left-click and hold to move.", 1, 1, 1 )
  280.                     Tooltip:AddLine( "Right-click to open " .. i .. " display settings.", 1, 1, 1 )
  281.                     if not H:IsDisplayActive( i, true ) then Tooltip:AddLine( "This display is not currently active.", 0.5, 0.5, 0.5 ) end
  282.                     Tooltip:Show()
  283.                 end
  284.             end )
  285.             v.Backdrop:SetScript( "OnLeave", function( self )
  286.                 Tooltip:Hide()
  287.             end )
  288.             v:Show()
  289.  
  290.             if not v.Header then
  291.                 v.Header = v.Backdrop:CreateFontString( "HekiliDisplay" .. i .. "Header", "OVERLAY", "GameFontNormal" )
  292.                 local path = v.Header:GetFont()
  293.                 v.Header:SetFont( path, 18, "OUTLINE" )
  294.             end
  295.             v.Header:ClearAllPoints()
  296.             v.Header:SetAllPoints( v.Backdrop )
  297.  
  298.             if i == "Defensives" then v.Header:SetText( AtlasToString( "nameplates-InterruptShield" ) )
  299.             elseif i == "Interrupts" then v.Header:SetText( AtlasToString( "voicechat-icon-speaker-mute" ) )
  300.             elseif i == "Cooldowns" then v.Header:SetText( AtlasToString( "chromietime-32x32" ) )
  301.             else v.Header:SetText( i ) end
  302.  
  303.             v.Header:SetJustifyH("CENTER")
  304.             v.Header:Show()
  305.         else
  306.             v:Hide()
  307.         end
  308.     end
  309.  
  310.     if not external then
  311.         if not Hekili.OptionsReady then Hekili:RefreshOptions() end
  312.  
  313.         local ACD = LibStub( "AceConfigDialog-3.0" )
  314.         ACD:SetDefaultSize( "Hekili", 800, 608 )
  315.         ACD:Open( "Hekili" )
  316.  
  317.         local oFrame = ACD.OpenFrames["Hekili"].frame
  318.         oFrame:SetResizeBounds( 800, 120 )
  319.  
  320.         ns.OnHideFrame = ns.OnHideFrame or CreateFrame( "Frame" )
  321.         ns.OnHideFrame:SetParent( oFrame )
  322.         ns.OnHideFrame:SetScript( "OnHide", function(self)
  323.             ns.StopConfiguration()
  324.             self:SetScript( "OnHide", nil )
  325.             self:SetParent( nil )
  326.             if not InCombatLockdown() then
  327.                 collectgarbage()
  328.                 Hekili:UpdateDisplayVisibility()
  329.             else
  330.                 C_Timer.After( 0, function() Hekili:UpdateDisplayVisibility() end )
  331.             end
  332.         end )
  333.  
  334.         if not ns.OnHideFrame.firstTime then
  335.             ACD:SelectGroup( "Hekili", "packs" )
  336.             ACD:SelectGroup( "Hekili", "displays" )
  337.             ACD:SelectGroup( "Hekili", "displays", "Multi" )
  338.             ACD:SelectGroup( "Hekili", "general" )
  339.             ns.OnHideFrame.firstTime = true
  340.         end
  341.  
  342.         Hekili:ProfileFrame( "CloseOptionsFrame", ns.OnHideFrame )
  343.     end
  344.  
  345.     Hekili:UpdateDisplayVisibility()
  346. end
  347.  
  348. function Hekili:OpenConfiguration()
  349.     ns.StartConfiguration()
  350. end
  351.  
  352. function ns.StopConfiguration()
  353.     Hekili.Config = false
  354.  
  355.     local scaleFactor = Hekili:GetScale()
  356.     local mouseInteract = Hekili.Pause
  357.  
  358.     for id, display in pairs( Hekili.DisplayPool ) do
  359.         display:EnableMouse( false )
  360.         if not display:IsAnchoringRestricted() then display:SetMovable( true ) end
  361.  
  362.         -- v:SetBackdrop( nil )
  363.         if display.Header then
  364.             display.Header:Hide()
  365.         end
  366.         if display.Backdrop then
  367.             display.Backdrop:Hide()
  368.         end
  369.  
  370.         for i, btn in ipairs( display.Buttons ) do
  371.             btn:EnableMouse( mouseInteract )
  372.             btn:SetMovable( false )
  373.         end
  374.     end
  375.  
  376.     HekiliNotification:EnableMouse( false )
  377.     HekiliNotification:SetMovable( false )
  378.     HekiliNotification.Mover:Hide()
  379.     -- HekiliNotification.Mover.Header:Hide()
  380. end
  381.  
  382. local function MasqueUpdate( Addon, Group, SkinID, Gloss, Backdrop, Colors, Disabled )
  383.     if Disabled then
  384.         for dispID, display in ipairs( ns.UI.Buttons ) do
  385.             for btnID, button in ipairs( display ) do
  386.                 button.__MSQ_NormalTexture:Hide()
  387.                 button.Texture:SetAllPoints( button )
  388.             end
  389.         end
  390.     end
  391. end
  392.  
  393.  
  394. do
  395.     ns.UI.Menu = ns.UI.Menu or CreateFrame( "Frame", "HekiliMenu", UIParent, "UIDropDownMenuTemplate" )
  396.     local menu = ns.UI.Menu
  397.  
  398.     Hekili:ProfileFrame( "HekiliMenu", menu )
  399.  
  400.     menu.info = {}
  401.  
  402.     menu.AddButton = UIDropDownMenu_AddButton
  403.     menu.AddSeparator = UIDropDownMenu_AddSeparator
  404.  
  405.     local function SetDisplayMode( mode )
  406.         Hekili.DB.profile.toggles.mode.value = mode
  407.         if WeakAuras and WeakAuras.ScanEvents then WeakAuras.ScanEvents( "HEKILI_TOGGLE", "mode", mode ) end
  408.         if ns.UI.Minimap then ns.UI.Minimap:RefreshDataText() end
  409.  
  410.         Hekili:UpdateDisplayVisibility()
  411.         Hekili:ForceUpdate( "HEKILI_TOGGLE", true )
  412.     end
  413.  
  414.     local function IsDisplayMode( p, mode )
  415.         return Hekili.DB.profile.toggles.mode.value == mode
  416.     end
  417.  
  418.     local menuData = {
  419.         {
  420.             isTitle = 1,
  421.             text = "Hekili",
  422.             notCheckable = 1,
  423.         },
  424.  
  425.         {
  426.             text = "Enable",
  427.             func = function () Hekili:Toggle() end,
  428.             checked = function () return Hekili.DB.profile.enabled end,
  429.         },
  430.  
  431.         {
  432.             text = "Pause",
  433.             func = function () return Hekili:TogglePause() end,
  434.             checked = function () return Hekili.Pause end,
  435.         },
  436.  
  437.         {
  438.             isSeparator = 1,
  439.         },
  440.  
  441.         {
  442.             isTitle = 1,
  443.             text = "Display Mode",
  444.             notCheckable = 1,
  445.         },
  446.  
  447.         {
  448.             text = "Auto",
  449.             func = function () SetDisplayMode( "automatic" ) end,
  450.             checked = function () return IsDisplayMode( p, "automatic" ) end,
  451.         },
  452.  
  453.         {
  454.             text = "Single",
  455.             func = function () SetDisplayMode( "single" ) end,
  456.             checked = function () return IsDisplayMode( p, "single" ) end,
  457.         },
  458.  
  459.         {
  460.             text = "AOE",
  461.             func = function () SetDisplayMode( "aoe" ) end,
  462.             checked = function () return IsDisplayMode( p, "aoe" ) end,
  463.         },
  464.  
  465.         {
  466.             text = "Dual",
  467.             func = function () SetDisplayMode( "dual" ) end,
  468.             checked = function () return IsDisplayMode( p, "dual" ) end,
  469.         },
  470.  
  471.         {
  472.             text = "Reactive",
  473.             func = function () SetDisplayMode( "reactive" ) end,
  474.             checked = function () return IsDisplayMode( p, "reactive" ) end,
  475.         },
  476.  
  477.         {
  478.             isSeparator = 1,
  479.         },
  480.  
  481.         {
  482.             isTitle = 1,
  483.             text = "Toggles",
  484.             notCheckable = 1,
  485.         },
  486.  
  487.         {
  488.             text = "Cooldowns",
  489.             func = function() Hekili:FireToggle( "cooldowns" ); ns.UI.Minimap:RefreshDataText() end,
  490.             checked = function () return Hekili.DB.profile.toggles.cooldowns.value end,
  491.         },
  492.  
  493.         {
  494.             text = "Minor CDs",
  495.             func = function() Hekili:FireToggle( "essences" ); ns.UI.Minimap:RefreshDataText() end,
  496.             checked = function () return Hekili.DB.profile.toggles.essences.value end,
  497.         },
  498.  
  499.         {
  500.             text = "Interrupts",
  501.             func = function() Hekili:FireToggle( "interrupts" ); ns.UI.Minimap:RefreshDataText() end,
  502.             checked = function () return Hekili.DB.profile.toggles.interrupts.value end,
  503.         },
  504.  
  505.         {
  506.             text = "Defensives",
  507.             func = function() Hekili:FireToggle( "defensives" ); ns.UI.Minimap:RefreshDataText() end,
  508.             checked = function () return Hekili.DB.profile.toggles.defensives.value end,
  509.         },
  510.  
  511.         {
  512.             text = "Potions",
  513.             func = function() Hekili:FireToggle( "potions" ); ns.UI.Minimap:RefreshDataText() end,
  514.             checked = function () return Hekili.DB.profile.toggles.potions.value end,
  515.         },
  516.  
  517.     }
  518.  
  519.     local specsParsed = false
  520.     menu.args = {}
  521.  
  522.     UIDropDownMenu_SetDisplayMode( menu, "MENU" )
  523.  
  524.     function menu:initialize( level, list )
  525.         if not level and not list then
  526.             return
  527.         end
  528.  
  529.         if level == 1 then
  530.             if not specsParsed then
  531.                 -- Add specialization toggles where applicable.
  532.                 for i, spec in pairs( Hekili.Class.specs ) do
  533.                     if i > 0 then
  534.                         insert( menuData, {
  535.                             isSeparator = 1,
  536.                             hidden = function () return Hekili.State.spec.id ~= i end,
  537.                         } )
  538.                         insert( menuData, {
  539.                             isTitle = 1,
  540.                             text = spec.name,
  541.                             notCheckable = 1,
  542.                             hidden = function () return Hekili.State.spec.id ~= i end,
  543.                         } )
  544.                         insert( menuData, {
  545.                             text = "|TInterface\\Addons\\Hekili\\Textures\\Cycle:0|t Recommend Target Swaps",
  546.                             tooltipTitle = "|TInterface\\Addons\\Hekili\\Textures\\Cycle:0|t Recommend Target Swaps",
  547.                             tooltipText = "If checked, the |TInterface\\Addons\\Hekili\\Textures\\Cycle:0|t indicator may be displayed which means you should use the ability on a different target.",
  548.                             tooltipOnButton = true,
  549.                             func = function ()
  550.                                 local spec = rawget( Hekili.DB.profile.specs, i )
  551.                                 if spec then
  552.                                     spec.cycle = not spec.cycle
  553.                                     if Hekili.DB.profile.notifications.enabled then
  554.                                         Hekili:Notify( "Recommend Target Swaps: " .. ( spec.cycle and "ON" or "OFF" ) )
  555.                                     else
  556.                                         Hekili:Print( "Recommend Target Swaps: " .. ( spec.cycle and " |cFF00FF00ENABLED|r." or " |cFFFF0000DISABLED|r." ) )
  557.                                     end
  558.                                 end
  559.                             end,
  560.                             checked = function ()
  561.                                 local spec = rawget( Hekili.DB.profile.specs, i )
  562.                                 return spec.cycle
  563.                             end,
  564.                             hidden = function () return Hekili.State.spec.id ~= i end,
  565.                         } )
  566.  
  567.                         -- Check for Toggles.
  568.                         for n, setting in pairs( spec.settings ) do
  569.                             if setting.info and ( not setting.info.arg or setting.info.arg() ) then
  570.                                 if setting.info.type == "toggle" then
  571.                                     local name = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name
  572.                                     local submenu
  573.                                     submenu = {
  574.                                         text = name,
  575.                                         tooltipTitle = name,
  576.                                         tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc,
  577.                                         tooltipOnButton = true,
  578.                                         func = function ()
  579.                                             menu.args[1] = setting.name
  580.                                             setting.info.set( menu.args, not setting.info.get( menu.args ) )
  581.  
  582.                                             local nm = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name
  583.  
  584.                                             if Hekili.DB.profile.notifications.enabled then
  585.                                                 Hekili:Notify( nm .. ": " .. ( setting.info.get( menu.args ) and "ON" or "OFF" ) )
  586.                                             else
  587.                                                 Hekili:Print( nm .. ": " .. ( setting.info.get( menu.args ) and " |cFF00FF00ENABLED|r." or " |cFFFF0000DISABLED|r." ) )
  588.                                             end
  589.  
  590.                                             submenu.text = nm
  591.                                             submenu.tooltipTitle = nm
  592.                                             submenu.tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc
  593.                                         end,
  594.                                         checked = function ()
  595.                                             menu.args[1] = setting.name
  596.                                             return setting.info.get( menu.args )
  597.                                         end,
  598.                                         hidden = function () return Hekili.State.spec.id ~= i end,
  599.                                     }
  600.                                     insert( menuData, submenu )
  601.  
  602.                                 elseif setting.info.type == "select" then
  603.                                     local name = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name
  604.                                     local submenu
  605.                                     submenu = {
  606.                                         text = name,
  607.                                         tooltipTitle = name,
  608.                                         tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc,
  609.                                         tooltipOnButton = true,
  610.                                         hasArrow = true,
  611.                                         menuList = {},
  612.                                         notCheckable = true,
  613.                                         hidden = function () return Hekili.State.spec.id ~= i end,
  614.                                     }
  615.  
  616.                                     local values = setting.info.values
  617.                                     if type( values ) == "function" then values = values() end
  618.  
  619.                                     if values then
  620.                                         if setting.info.sorting then
  621.                                             for _, k in orderedPairs( setting.info.sorting ) do
  622.                                                 local v = values[ k ]
  623.                                                 insert( submenu.menuList, {
  624.                                                     text = v,
  625.                                                     func = function ()
  626.                                                         menu.args[1] = setting.name
  627.                                                         setting.info.set( menu.args, k )
  628.  
  629.                                                         local nm = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name
  630.                                                         submenu.text = nm
  631.                                                         submenu.tooltipTitle = nm
  632.                                                         submenu.tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc
  633.  
  634.                                                         for k, v in pairs( Hekili.DisplayPool ) do
  635.                                                             v:OnEvent( "HEKILI_MENU" )
  636.                                                         end
  637.                                                     end,
  638.                                                     checked = function ()
  639.                                                         menu.args[1] = setting.name
  640.                                                         return setting.info.get( menu.args ) == k
  641.                                                     end,
  642.                                                     hidden = function () return Hekili.State.spec.id ~= i end,
  643.                                                 } )
  644.                                             end
  645.                                         else
  646.                                             for k, v in orderedPairs( values ) do
  647.                                                 insert( submenu.menuList, {
  648.                                                     text = v,
  649.                                                     func = function ()
  650.                                                         menu.args[1] = setting.name
  651.                                                         setting.info.set( menu.args, k )
  652.  
  653.                                                         local nm = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name
  654.                                                         submenu.text = nm
  655.                                                         submenu.tooltipTitle = nm
  656.                                                         submenu.tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc
  657.  
  658.                                                         for k, v in pairs( Hekili.DisplayPool ) do
  659.                                                             v:OnEvent( "HEKILI_MENU" )
  660.                                                         end
  661.                                                     end,
  662.                                                     checked = function ()
  663.                                                         menu.args[1] = setting.name
  664.                                                         return setting.info.get( menu.args ) == k
  665.                                                     end,
  666.                                                     hidden = function () return Hekili.State.spec.id ~= i end,
  667.                                                 } )
  668.                                             end
  669.                                         end
  670.                                     end
  671.  
  672.                                     insert( menuData, submenu )
  673.  
  674.                                 elseif setting.info.type == "range" then
  675.  
  676.                                     local submenu = {
  677.                                         text = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name,
  678.                                         tooltipTitle = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name,
  679.                                         tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc,
  680.                                         tooltipOnButton = true,
  681.                                         notCheckable = true,
  682.                                         hidden = function () return Hekili.State.spec.id ~= i end,
  683.                                         hasArrow = true,
  684.                                         menuList = {}
  685.                                     }
  686.  
  687.                                     local slider = {
  688.                                         text = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name,
  689.                                         tooltipTitle = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name,
  690.                                         tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc,
  691.                                         tooltipOnButton = true,
  692.                                         notCheckable = true,
  693.                                         hidden = function () return Hekili.State.spec.id ~= i end,
  694.                                     }
  695.                                     local cn = "HekiliSpec" .. i .. "Option" .. n
  696.                                     local cf = CreateFrame( "Frame", cn, UIParent, "HekiliPopupDropdownRangeTemplate" )
  697.  
  698.                                     cf.Slider:SetAccessorFunction( function()
  699.                                         menu.args[1] = setting.name
  700.                                         return setting.info.get( menu.args )
  701.                                     end )
  702.  
  703.                                     cf.Slider:SetMutatorFunction( function( val )
  704.                                         menu.args[1] = setting.name
  705.                                         return setting.info.set( menu.args, val )
  706.                                     end )
  707.  
  708.                                     cf.Slider:SetMinMaxValues( setting.info.min, setting.info.max )
  709.                                     cf.Slider:SetValueStep( setting.info.step or 1 )
  710.                                     cf.Slider:SetObeyStepOnDrag( true )
  711.  
  712.                                     cf.Slider:SetScript( "OnEnter", function( self )
  713.                                         local tooltip = GetAppropriateTooltip()
  714.                                         tooltip:SetOwner( cf.Slider, "ANCHOR_RIGHT", 0, 2 )
  715.                                         GameTooltip_SetTitle( tooltip, slider.tooltipTitle )
  716.                                         GameTooltip_AddNormalLine( tooltip, slider.tooltipText, true )
  717.                                         tooltip:Show()
  718.                                     end )
  719.  
  720.                                     cf.Slider:SetScript( "OnLeave", function( self )
  721.                                         GameTooltip:Hide()
  722.                                     end )
  723.  
  724.                                     slider.customFrame = cf
  725.  
  726.                                     insert( submenu.menuList, slider )
  727.  
  728.                                     --[[ local low, high, step = setting.info.min, setting.info.max, setting.info.step
  729.                                     local fractional, factor = step < 1, 1 / step
  730.  
  731.                                     if fractional then
  732.                                         low = low * factor
  733.                                         high = high * factor
  734.                                         step = step * factor
  735.                                     end
  736.  
  737.                                     if ceil( ( high - low ) / step ) > 20 then
  738.                                         step = ceil( ( high - low ) / 20 )
  739.                                         if step % ( setting.info.step or 1 ) ~= 0 then
  740.                                             step = step - ( step % ( setting.info.step or 1 ) )
  741.                                         end
  742.                                     end
  743.  
  744.                                     for j = low, high, step do
  745.                                         local actual = j / factor
  746.                                         insert( submenu.menuList, {
  747.                                             text = tostring( actual ),
  748.                                             func = function ()
  749.                                                 menu.args[1] = setting.name
  750.                                                 setting.info.set( menu.args, actual )
  751.  
  752.                                                 local name = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name
  753.  
  754.                                                 if Hekili.DB.profile.notifications.enabled then
  755.                                                     Hekili:Notify( name .. " set to |cFF00FF00" .. actual .. "|r." )
  756.                                                 else
  757.                                                     Hekili:Print( name .. " set to |cFF00FF00" .. actual .. "|r." )
  758.                                                 end
  759.                                             end,
  760.                                             checked = function ()
  761.                                                 menu.args[1] = setting.name
  762.                                                 return setting.info.get( menu.args ) == actual
  763.                                             end,
  764.                                             hidden = function () return Hekili.State.spec.id ~= i end,
  765.                                         } )
  766.                                     end ]]
  767.  
  768.                                     insert( menuData, submenu )
  769.                                 end
  770.                             end
  771.                         end
  772.                     end
  773.                 end
  774.                 specsParsed = true
  775.             end
  776.         end
  777.  
  778.         local use = list or menuData
  779.         local classic = Hekili.IsClassic()
  780.  
  781.         for i, data in ipairs( use ) do
  782.             data.classicChecks = classic
  783.  
  784.             if not data.hidden or ( type( data.hidden ) == 'function' and not data.hidden() ) then
  785.                 if data.isSeparator then
  786.                     menu.AddSeparator( level )
  787.                 else
  788.                     menu.AddButton( data, level )
  789.                 end
  790.             end
  791.         end
  792.     end
  793. end
  794.  
  795.  
  796.  
  797.  
  798.  
  799. do
  800.     ns.UI.Displays = ns.UI.Displays or {}
  801.     local dPool = ns.UI.Displays
  802.     Hekili.DisplayPool = dPool
  803.  
  804.     local alphaUpdateEvents = {
  805.         PET_BATTLE_OPENING_START = 1,
  806.         PET_BATTLE_CLOSE = 1,
  807.         BARBER_SHOP_OPEN = 1,
  808.         BARBER_SHOP_CLOSE = 1,
  809.  
  810.         PLAYER_GAINS_VEHICLE_DATA = 1,
  811.         PLAYER_LOSES_VEHICLE_DATA = 1,
  812.         UNIT_ENTERING_VEHICLE = 1,
  813.         UNIT_ENTERED_VEHICLE = 1,
  814.         UNIT_EXITED_VEHICLE = 1,
  815.         UNIT_EXITING_VEHICLE = 1,
  816.         VEHICLE_ANGLE_SHOW = 1,
  817.         VEHICLE_UPDATE = 1,
  818.         UPDATE_VEHICLE_ACTIONBAR = 1,
  819.         UPDATE_OVERRIDE_ACTIONBAR = 1,
  820.         CLIENT_SCENE_OPENED = 1,
  821.         CLIENT_SCENE_CLOSED = 1,
  822.         -- UNIT_FLAGS = 1,
  823.  
  824.         PLAYER_TARGET_CHANGED = 1,
  825.  
  826.         PLAYER_ENTERING_WORLD = 1,
  827.         PLAYER_REGEN_ENABLED = 1,
  828.         PLAYER_REGEN_DISABLED = 1,
  829.  
  830.         ACTIVE_TALENT_GROUP_CHANGED = 1,
  831.  
  832.         ZONE_CHANGED = 1,
  833.         ZONE_CHANGED_INDOORS = 1,
  834.         ZONE_CHANGED_NEW_AREA = 1,
  835.  
  836.         PLAYER_CONTROL_LOST = 1,
  837.         PLAYER_CONTROL_GAINED = 1,
  838.  
  839.         PLAYER_MOUNT_DISPLAY_CHANGED = 1,
  840.         UPDATE_ALL_UI_WIDGETS = 1,
  841.     }
  842.  
  843.     local kbEvents = {
  844.         -- ACTIONBAR_SLOT_CHANGED = 1,
  845.         ACTIONBAR_PAGE_CHANGED = 1,
  846.         ACTIONBAR_UPDATE_STATE = 1,
  847.         SPELLS_CHANGED = 1,
  848.         UPDATE_SHAPESHIFT_FORM = 1,
  849.     }
  850.  
  851.     local flashEvents = {
  852.         -- This unregisters flash frames in SpellFlash.
  853.         ACTIONBAR_SHOWGRID = 1,
  854.  
  855.         -- These re-register flash frames in SpellFlash (after 0.5 - 1.0s).
  856.         ACTIONBAR_HIDEGRID = 1,
  857.         LEARNED_SPELL_IN_TAB = 1,
  858.         CHARACTER_POINTS_CHANGED = 1,
  859.         ACTIVE_TALENT_GROUP_CHANGED = 1,
  860.         UPDATE_MACROS = 1,
  861.         VEHICLE_UPDATE = 1,
  862.     }
  863.  
  864.     local pulseAuras = 0.1
  865.     local pulseDelay = 0.05
  866.     local pulseGlow = 0.25
  867.     local pulseTargets = 0.1
  868.     local pulseRange = TOOLTIP_UPDATE_TIME
  869.     local pulseFlash = 0.5
  870.  
  871.     local flashOffset = {
  872.         Primary = 0,
  873.         AOE = 0.25,
  874.         Interrupts = 0.125,
  875.         Defensives = 0.333,
  876.         Cooldowns = 0.416
  877.     }
  878.  
  879.     local oocRefresh = 1
  880.     local icRefresh = {
  881.         Primary = 0.25,
  882.         AOE = 0.25,
  883.         Interrupts = 0.25,
  884.         Defensives = 0.5,
  885.         Cooldowns = 0.25
  886.     }
  887.  
  888.     local LRC = LibStub( "LibRangeCheck-3.0" )
  889.     local LSF = SpellFlashCore
  890.     local catchFlash, lastFramesFlashed = nil, {}
  891.  
  892.     if LSF then
  893.         hooksecurefunc( LSF, "FlashFrame", function( frame )
  894.             local flash = frame and frame.SpellFlashCoreAddonFlashFrame
  895.  
  896.             -- We need to know what flashed so we can force it to stop flashing when the recommendation changes.
  897.             if catchFlash and flash then
  898.                 lastFramesFlashed[ flash ] = 1
  899.             end
  900.         end )
  901.     end
  902.  
  903.     local LSR = LibStub("SpellRange-1.0")
  904.     local Glower = LibStub("LibCustomGlow-1.0")
  905.  
  906.     local function CalculateAlpha( id )
  907.         if IsInPetBattle() or Hekili.Barber or Hekili.ClientScene or UnitHasVehicleUI( "player" ) or HasVehicleActionBar() or HasOverrideActionBar() or UnitOnTaxi( "player" ) or not Hekili:IsDisplayActive( id ) then
  908.             return 0
  909.         end
  910.  
  911.         local prof = Hekili.DB.profile
  912.         local conf = prof.displays[ id ]
  913.         local spec = state.spec.id and prof.specs[ state.spec.id ]
  914.         local aoe  = spec and spec.aoe or 3
  915.  
  916.         local _, zoneType = IsInInstance()
  917.  
  918.         if not conf.enabled then
  919.             return 0
  920.  
  921.         elseif id == "AOE" and Hekili:GetToggleState( "mode" ) == "reactive" and Hekili:GetNumTargets() < aoe then
  922.             return 0
  923.  
  924.         elseif zoneType == "pvp" or zoneType == "arena" then
  925.             if not conf.visibility.advanced then return conf.visibility.pvp.alpha end
  926.  
  927.             if conf.visibility.pvp.hideMounted and IsMounted() then return 0 end
  928.  
  929.             if conf.visibility.pvp.combatTarget > 0 and state.combat > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then
  930.                 return conf.visibility.pvp.combatTarget
  931.             elseif conf.visibility.pvp.combat > 0 and state.combat > 0 then
  932.                 return conf.visibility.pvp.combat
  933.             elseif conf.visibility.pvp.target > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then
  934.                 return conf.visibility.pvp.target
  935.             elseif conf.visibility.pvp.always > 0 then
  936.                 return conf.visibility.pvp.always
  937.             end
  938.  
  939.             return 0
  940.         end
  941.  
  942.         if not conf.visibility.advanced then return conf.visibility.pve.alpha end
  943.  
  944.         if conf.visibility.pve.hideMounted and IsMounted() then return 0 end
  945.  
  946.         if conf.visibility.pve.combatTarget > 0 and state.combat > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then
  947.             return conf.visibility.pve.combatTarget
  948.         elseif conf.visibility.pve.combat > 0 and state.combat > 0 then
  949.             return conf.visibility.pve.combat
  950.         elseif conf.visibility.pve.target > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then
  951.             return conf.visibility.pve.target
  952.         elseif conf.visibility.pve.always > 0 then
  953.             return conf.visibility.pve.always
  954.         end
  955.  
  956.         return 0
  957.     end
  958.  
  959.     local numDisplays = 0
  960.  
  961.     function Hekili:CreateDisplay( id )
  962.         local conf = rawget( self.DB.profile.displays, id )
  963.         if not conf then return end
  964.  
  965.         if not dPool[ id ] then
  966.             numDisplays = numDisplays + 1
  967.             dPool[ id ] = CreateFrame( "Frame", "HekiliDisplay" .. id, UIParent )
  968.             dPool[ id ].index = numDisplays
  969.  
  970.             Hekili:ProfileFrame( "HekiliDisplay" .. id, dPool[ id ] )
  971.         end
  972.         local d = dPool[ id ]
  973.  
  974.         d.id = id
  975.         d.alpha = 0
  976.         d.numIcons = conf.numIcons
  977.         d.firstForce = 0
  978.         d.threadLocked = false
  979.  
  980.         local scale = self:GetScale()
  981.         local border = 2
  982.  
  983.         d:SetSize( scale * ( border + ( conf.primaryWidth or 50 ) ), scale * ( border + ( conf.primaryHeight or 50 ) ) )
  984.         --[[ d:SetIgnoreParentScale( true )
  985.         d:SetScale( UIParent:GetScale() ) ]]
  986.         d:ClearAllPoints()
  987.  
  988.         d:SetPoint( "CENTER", UIParent, "CENTER", conf.x or 0, conf.y or -225 )
  989.         d:SetParent( UIParent )
  990.  
  991.         d:SetFrameStrata( conf.frameStrata or "MEDIUM" )
  992.         d:SetFrameLevel( conf.frameLevel or ( 10 * d.index ) )
  993.  
  994.         if not d:IsAnchoringRestricted() then
  995.             d:SetClampedToScreen( true )
  996.             d:EnableMouse( false )
  997.             d:SetMovable( true )
  998.         end
  999.  
  1000.         function d:UpdateKeybindings()
  1001.             local conf = Hekili.DB.profile.displays[ self.id ]
  1002.  
  1003.             if conf.keybindings and conf.keybindings.enabled then
  1004.                 for i, b in ipairs( self.Buttons ) do
  1005.                     local a = b.Action
  1006.  
  1007.                     if a then
  1008.                         b.Keybind, b.KeybindFrom = Hekili:GetBindingForAction( a, conf, i )
  1009.  
  1010.                         if i == 1 or conf.keybindings.queued then
  1011.                             b.Keybinding:SetText( b.Keybind )
  1012.                         else
  1013.                             b.Keybinding:SetText( nil )
  1014.                         end
  1015.                     else
  1016.                         b.Keybinding:SetText( nil )
  1017.                     end
  1018.                 end
  1019.             end
  1020.         end
  1021.  
  1022.         function d:IsThreadLocked()
  1023.             return self.threadLocked
  1024.         end
  1025.  
  1026.         function d:SetThreadLocked( locked )
  1027.             self.threadLocked = locked
  1028.         end
  1029.  
  1030.  
  1031.         local RomanNumerals = {
  1032.             "I",
  1033.             "II",
  1034.             "III",
  1035.             "IV"
  1036.         }
  1037.  
  1038.  
  1039.         function d:OnUpdate( elapsed )
  1040.             if not self.Recommendations or not Hekili.PLAYER_ENTERING_WORLD then
  1041.                 return
  1042.             end
  1043.  
  1044.             local init = debugprofilestop()
  1045.  
  1046.             local profile = Hekili.DB.profile
  1047.             local conf = profile.displays[ self.id ]
  1048.  
  1049.             self.alphaCheck = self.alphaCheck - elapsed
  1050.  
  1051.             if self.alphaCheck <= 0 then
  1052.                 self.alphaCheck = 0.5
  1053.                 self:UpdateAlpha()
  1054.             end
  1055.  
  1056.             if not self.id == "Primary" and not ( self.Buttons[ 1 ] and self.Buttons[ 1 ].Action ) and not ( self.HasRecommendations or not self.NewRecommendations ) then
  1057.                 return
  1058.             end
  1059.  
  1060.             local postAlpha = debugprofilestop()
  1061.  
  1062.             if Hekili.Pause and not self.paused then
  1063.                 self.Buttons[ 1 ].Overlay:Show()
  1064.                 self.paused = true
  1065.             elseif not Hekili.Pause and self.paused then
  1066.                 self.Buttons[ 1 ].Overlay:Hide()
  1067.                 self.paused = false
  1068.             end
  1069.  
  1070.             local now = GetTime()
  1071.  
  1072.             self.recTimer = self.recTimer - elapsed
  1073.  
  1074.             if not self:IsThreadLocked() and ( self.NewRecommendations or self.recTimer < 0 ) then
  1075.                 local alpha = self.alpha
  1076.                 local options = Hekili:GetActiveSpecOption( "abilities" )
  1077.  
  1078.                 if self.HasRecommendations and self.RecommendationsStr and self.RecommendationsStr:len() == 0 then
  1079.                     for i, b in ipairs( self.Buttons ) do b:Hide() end
  1080.                     self.HasRecommendations = false
  1081.                 else
  1082.                     self.HasRecommendations = true
  1083.  
  1084.                     for i, b in ipairs( self.Buttons ) do
  1085.                         b.Recommendation = self.Recommendations[ i ]
  1086.  
  1087.                         local action = b.Recommendation.actionName
  1088.                         local caption = b.Recommendation.caption
  1089.                         local indicator = b.Recommendation.indicator
  1090.                         local keybind = b.Recommendation.keybind
  1091.                         local exact_time = b.Recommendation.exact_time
  1092.  
  1093.                         local ability = class.abilities[ action ]
  1094.  
  1095.                         if ability then
  1096.                             if ( conf.flash.enabled and conf.flash.suppress ) then b:Hide()
  1097.                             else b:Show() end
  1098.  
  1099.                             if i == 1 then
  1100.                                 -- print( "Changing", GetTime() )
  1101.                             end
  1102.  
  1103.                             if action ~= b.lastAction or self.NewRecommendations or not b.Image then
  1104.                                 if ability.item then
  1105.                                     b.Image = b.Recommendation.texture or ability.texture or select( 10, GetItemInfo( ability.item ) )
  1106.                                 else
  1107.                                     local override = options and rawget( options, action )
  1108.                                     b.Image = override and override.icon or b.Recommendation.texture or ability.texture or GetSpellTexture( ability.id )
  1109.                                 end
  1110.                                 b.Texture:SetTexture( b.Image )
  1111.                                 b.Texture:SetTexCoord( unpack( b.texCoords ) )
  1112.                                 b.lastAction = action
  1113.                             end
  1114.  
  1115.                             b.Texture:Show()
  1116.  
  1117.                             if i == 1 then
  1118.                                 if conf.glow.highlight then
  1119.                                     local id = ability.item or ability.id
  1120.                                     local isItem = ability.item ~= nil
  1121.  
  1122.                                     if id and ( isItem and IsCurrentItem( id ) or IsCurrentSpell( id ) ) and exact_time > GetTime() then
  1123.                                         b.Highlight:Show()
  1124.                                     else
  1125.                                         b.Highlight:Hide()
  1126.                                     end
  1127.  
  1128.                                 elseif b.Highlight:IsShown() then
  1129.                                     b.Highlight:Hide()
  1130.                                 end
  1131.                             end
  1132.  
  1133.  
  1134.                             if ability.empowered then
  1135.                                 b.EmpowerLevel:SetText( RomanNumerals[ b.Recommendation.empower_to or state.max_empower ] )
  1136.                             else
  1137.                                 b.EmpowerLevel:SetText( nil )
  1138.                             end
  1139.  
  1140.                             if conf.indicators.enabled and indicator then
  1141.                                 if indicator == "cycle" then
  1142.                                     b.Icon:SetTexture("Interface\\Addons\\Hekili\\Textures\\Cycle")
  1143.                                 end
  1144.                                 if indicator == "cancel" then
  1145.                                     b.Icon:SetTexture("Interface\\Addons\\Hekili\\Textures\\Cancel")
  1146.                                 end
  1147.                                 b.Icon:Show()
  1148.                             else
  1149.                                 b.Icon:Hide()
  1150.                             end
  1151.  
  1152.                             if ( caption and conf.captions.enabled or ability.caption and not ability.empowered ) and ( i == 1 or conf.captions.queued ) then
  1153.                                 b.Caption:SetText( caption )
  1154.                             else
  1155.                                 b.Caption:SetText(nil)
  1156.                             end
  1157.  
  1158.                             if conf.keybindings.enabled and ( i == 1 or conf.keybindings.queued ) then
  1159.                                 b.Keybinding:SetText( keybind )
  1160.                             else
  1161.                                 b.Keybinding:SetText(nil)
  1162.                             end
  1163.  
  1164.                             if conf.glow.enabled and ( i == 1 or conf.glow.queued ) and IsSpellOverlayed( ability.id ) then
  1165.                                 b.glowColor = b.glowColor or {}
  1166.  
  1167.                                 if conf.glow.coloring == "class" then
  1168.                                     b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = RAID_CLASS_COLORS[ class.file ]:GetRGBA()
  1169.                                 elseif conf.glow.coloring == "custom" then
  1170.                                     b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = unpack(conf.glow.color)
  1171.                                 else
  1172.                                     b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = 0.95, 0.95, 0.32, 1
  1173.                                 end
  1174.  
  1175.                                 if conf.glow.mode == "default" then
  1176.                                     Glower.ButtonGlow_Start( b, b.glowColor )
  1177.                                     b.glowStop = Glower.ButtonGlow_Stop
  1178.                                 elseif conf.glow.mode == "autocast" then
  1179.                                     Glower.AutoCastGlow_Start( b, b.glowColor )
  1180.                                     b.glowStop = Glower.AutoCastGlow_Stop
  1181.                                 elseif conf.glow.mode == "pixel" then
  1182.                                     Glower.PixelGlow_Start( b, b.glowColor )
  1183.                                     b.glowStop = Glower.PixelGlow_Stop
  1184.                                 end
  1185.  
  1186.                                 b.glowing = true
  1187.                             elseif b.glowing then
  1188.                                 if b.glowStop then b:glowStop() end
  1189.                                 b.glowing = false
  1190.                             end
  1191.                         else
  1192.                             b:Hide()
  1193.                         end
  1194.  
  1195.                         b.Action = action
  1196.                         b.Text = caption
  1197.                         b.Indicator = indicator
  1198.                         b.Keybind = keybind
  1199.                         b.Ability = ability
  1200.                         b.ExactTime = exact_time
  1201.                     end
  1202.  
  1203.                     self.glowTimer = -1
  1204.                     self.rangeTimer = -1
  1205.                     self.delayTimer = -1
  1206.  
  1207.                     self.recTimer = 1
  1208.                     self.alphaCheck = 0.5
  1209.  
  1210.                     self:RefreshCooldowns( "RECS_UPDATED" )
  1211.                 end
  1212.             end
  1213.  
  1214.             local postRecs = debugprofilestop()
  1215.  
  1216.             if self.HasRecommendations then
  1217.                 self.glowTimer = self.glowTimer - elapsed
  1218.  
  1219.                 if self.glowTimer < 0 or self.NewRecommendations then
  1220.                     if conf.glow.enabled then
  1221.                         for i, b in ipairs( self.Buttons ) do
  1222.                             if not b.Action then break end
  1223.  
  1224.                             local a = b.Ability
  1225.  
  1226.                             if i == 1 or conf.glow.queued then
  1227.                                 local glowing = a.id > 0 and IsSpellOverlayed( a.id )
  1228.  
  1229.                                 if glowing and not b.glowing then
  1230.                                     b.glowColor = b.glowColor or {}
  1231.  
  1232.                                     if conf.glow.coloring == "class" then
  1233.                                         b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = RAID_CLASS_COLORS[ class.file ]:GetRGBA()
  1234.                                     elseif conf.glow.coloring == "custom" then
  1235.                                         b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = unpack(conf.glow.color)
  1236.                                     else
  1237.                                         b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = 0.95, 0.95, 0.32, 1
  1238.                                     end
  1239.  
  1240.                                     if conf.glow.mode == "default" then
  1241.                                         Glower.ButtonGlow_Start( b, b.glowColor )
  1242.                                         b.glowStop = Glower.ButtonGlow_Stop
  1243.                                     elseif conf.glow.mode == "autocast" then
  1244.                                         Glower.AutoCastGlow_Start( b, b.glowColor )
  1245.                                         b.glowStop = Glower.AutoCastGlow_Stop
  1246.                                     elseif conf.glow.mode == "pixel" then
  1247.                                         Glower.PixelGlow_Start( b, b.glowColor )
  1248.                                         b.glowStop = Glower.PixelGlow_Stop
  1249.                                     end
  1250.  
  1251.                                     b.glowing = true
  1252.                                 elseif not glowing and b.glowing then
  1253.                                     b:glowStop()
  1254.                                     b.glowing = false
  1255.                                 end
  1256.                             else
  1257.                                 if b.glowing then
  1258.                                     b:glowStop()
  1259.                                     b.glowing = false
  1260.                                 end
  1261.                             end
  1262.                         end
  1263.                     end
  1264.                 end
  1265.  
  1266.                 local postGlow = debugprofilestop()
  1267.  
  1268.                 self.rangeTimer = self.rangeTimer - elapsed
  1269.  
  1270.                 if self.rangeTimer < 0 or self.NewRecommendations then
  1271.                     for i, b in ipairs( self.Buttons ) do
  1272.                         local a = b.Ability
  1273.  
  1274.                         if a and a.id then
  1275.                             local outOfRange = false
  1276.  
  1277.                             if conf.range.enabled and UnitCanAttack( "player", "target" ) then
  1278.                                 if conf.range.type == "melee" then
  1279.                                     outOfRange = ( LRC:GetRange( "target" ) or 10 ) > 7
  1280.                                 elseif conf.range.type == "ability" then
  1281.                                     local name = a.rangeSpell or a.itemSpellName or a.actualName or a.name
  1282.                                     if name then outOfRange = LSR.IsSpellInRange( name, "target" ) == 0 end
  1283.                                 end
  1284.                             end
  1285.  
  1286.                             if outOfRange and not b.outOfRange then
  1287.                                 b.Texture:SetDesaturated(true)
  1288.                                 b.Texture:SetVertexColor(1.0, 0.0, 0.0, 1.0)
  1289.                                 b.outOfRange = true
  1290.                             elseif b.outOfRange and not outOfRange then
  1291.                                 b.Texture:SetDesaturated(false)
  1292.                                 b.Texture:SetVertexColor(1.0, 1.0, 1.0, 1.0)
  1293.                                 b.outOfRange = false
  1294.                             end
  1295.  
  1296.                             if not b.outOfRange then
  1297.                                 local _, unusable
  1298.  
  1299.                                 if a.itemCd or a.item then
  1300.                                     unusable = not IsUsableItem( a.itemCd or a.item )
  1301.                                 else
  1302.                                     _, unusable = IsUsableSpell( a.actualName or a.name )
  1303.                                 end
  1304.  
  1305.                                 if i == 1 and conf.delays.fade then
  1306.                                     local delay = b.ExactTime and ( b.ExactTime - now ) or 0
  1307.                                     --[[ local start, duration = 0, 0
  1308.  
  1309.                                     if a.gcd ~= "off" then
  1310.                                         start, duration = GetSpellCooldown( 61304 )
  1311.                                         if start > 0 then moment = start + duration - now end
  1312.                                     end
  1313.  
  1314.                                     local rStart, rDuration
  1315.                                     if a.item then
  1316.                                         rStart, rDuration = GetItemCooldown( a.item )
  1317.                                     else
  1318.                                         rStart, rDuration = GetSpellCooldown( a.id )
  1319.                                     end
  1320.                                     if rStart > 0 then moment = max( moment, rStart + rDuration - now ) end
  1321.  
  1322.                                     start, duration = select( 4, UnitCastingInfo( "player" ) )
  1323.                                     if start and start > 0 then moment = max( ( start / 1000 ) + ( duration / 1000 ) - now, moment ) end ]]
  1324.  
  1325.                                     if delay > 0.05 then
  1326.                                         unusable = true
  1327.                                     end
  1328.                                 end
  1329.  
  1330.                                 if unusable and not b.unusable then
  1331.                                     b.Texture:SetVertexColor(0.4, 0.4, 0.4, 1.0)
  1332.                                     b.unusable = true
  1333.                                 elseif b.unusable and not unusable then
  1334.                                     b.Texture:SetVertexColor(1.0, 1.0, 1.0, 1.0)
  1335.                                     b.unusable = false
  1336.                                 end
  1337.                             end
  1338.                         end
  1339.                     end
  1340.  
  1341.                     self.rangeTimer = pulseRange
  1342.                 end
  1343.  
  1344.                 local postRange = debugprofilestop()
  1345.  
  1346.                 if self.flashReady and conf.flash.enabled and LSF and ( InCombatLockdown() or not conf.flash.combat ) then
  1347.                     self.flashTimer = self.flashTimer - elapsed
  1348.                     self.flashWarnings = self.flashWarnings or {}
  1349.                     self.lastFlashFrames = self.lastFlashFrames or {}
  1350.  
  1351.                     local a = self.Buttons[ 1 ].Action
  1352.                     local changed = self.lastFlash ~= a
  1353.  
  1354.                     if a and ( changed or self.flashTimer < 0 ) then
  1355.                         if changed then
  1356.                             for frame in pairs( self.lastFlashFrames ) do
  1357.                                 frame:Hide()
  1358.                                 frame.flashDuration = 0
  1359.                                 self.lastFlashFrames[ frame ] = nil
  1360.                             end
  1361.                         end
  1362.  
  1363.                         self.flashTimer = conf.flash.speed or 0.4
  1364.  
  1365.                         local ability = class.abilities[ a ]
  1366.  
  1367.                         self.flashColor = self.flashColor or {}
  1368.                         self.flashColor.r, self.flashColor.g, self.flashColor.b = unpack( conf.flash.color )
  1369.  
  1370.                         catchFlash = GetTime()
  1371.                         table.wipe( lastFramesFlashed )
  1372.  
  1373.                         if ability.item then
  1374.                             local iname = LSF.ItemName( ability.item )
  1375.                             if LSF.Flashable( iname ) then
  1376.                                 LSF.FlashItem( iname, self.flashColor, conf.flash.size, conf.flash.brightness, conf.flash.blink, nil, profile.flashTexture, conf.flash.fixedSize, conf.flash.fixedBrightness )
  1377.                             elseif conf.flash.suppress and not self.flashWarnings[ iname ] then
  1378.                                 self.flashWarnings[ iname ] = true
  1379.                                 -- Hekili:Error( "|cffff0000WARNING|r - Could not flash recommended item '" .. iname .. "' (" .. self.id .. ")." )
  1380.                             end
  1381.                         else
  1382.                             local aFlash = ability.flash
  1383.                             if aFlash then
  1384.                                 local flashable = false
  1385.  
  1386.                                 if type( aFlash ) == "table" then
  1387.                                     local lastSpell
  1388.                                     for _, spell in ipairs( aFlash ) do
  1389.                                         lastSpell = spell
  1390.                                         if LSF.Flashable( spell ) then
  1391.                                             flashable = true
  1392.                                             break
  1393.                                         end
  1394.                                     end
  1395.                                     aFlash = lastSpell
  1396.                                 else
  1397.                                     flashable = LSF.Flashable( aFlash )
  1398.                                 end
  1399.  
  1400.                                 if flashable then
  1401.                                     LSF.FlashAction( aFlash, self.flashColor, conf.flash.size, conf.flash.brightness, conf.flash.blink, nil, profile.flashTexture, conf.flash.fixedSize, conf.flash.fixedBrightness )
  1402.                                 elseif conf.flash.suppress and not self.flashWarnings[ aFlash ] then
  1403.                                     self.flashWarnings[ aFlash ] = true
  1404.                                     -- Hekili:Error( "|cffff0000WARNING|r - Could not flash recommended action '" .. aFlash .. "' (" .. self.id .. ")." )
  1405.                                 end
  1406.                             else
  1407.                                 local id = ability.known
  1408.  
  1409.                                 if id == nil or type( id ) ~= "number" then
  1410.                                     id = ability.id
  1411.                                 end
  1412.  
  1413.                                 local sname = LSF.SpellName( id )
  1414.  
  1415.                                 if sname then
  1416.                                     if LSF.Flashable( sname ) then
  1417.                                         LSF.FlashAction( sname, self.flashColor, conf.flash.size, conf.flash.brightness, conf.flash.blink, nil, profile.flashTexture, conf.flash.fixedSize, conf.flash.fixedBrightness )
  1418.                                     elseif not self.flashWarnings[ sname ] then
  1419.                                         self.flashWarnings[ sname ] = true
  1420.                                         -- Hekili:Error( "|cffff0000WARNING|r - Could not flash recommended ability '" .. sname .. "' (" .. self.id .. ")." )
  1421.                                     end
  1422.                                 end
  1423.                             end
  1424.                         end
  1425.  
  1426.                         catchFlash = nil
  1427.                         for frame, status in pairs( lastFramesFlashed ) do
  1428.                             if status ~= 0 then
  1429.                                 self.lastFlashFrames[ frame ] = 1
  1430.                                 if frame.texture ~= profile.flashTexture then
  1431.                                     frame.FlashTexture:SetTexture( profile.flashTexture )
  1432.                                     frame.texture = profile.flashTexture
  1433.                                 end
  1434.                             end
  1435.                         end
  1436.                         self.lastFlash = a
  1437.                     end
  1438.                 end
  1439.  
  1440.                 local postFlash = debugprofilestop()
  1441.  
  1442.                 self.targetTimer = self.targetTimer - elapsed
  1443.  
  1444.                 if self.targetTimer < 0 or self.NewRecommendations then
  1445.                     local b = self.Buttons[ 1 ]
  1446.  
  1447.                     if conf.targets.enabled then
  1448.                         local tMin, tMax = 0, 0
  1449.                         local mode = profile.toggles.mode.value
  1450.                         local spec = state.spec.id and profile.specs[ state.spec.id ]
  1451.  
  1452.                         if self.id == 'Primary' then
  1453.                             if ( mode == 'dual' or mode == 'single' or mode == 'reactive' ) then tMax = 1
  1454.                             elseif mode == 'aoe' then tMin = spec and spec.aoe or 3 end
  1455.                         elseif self.id == 'AOE' then tMin = spec and spec.aoe or 3 end
  1456.  
  1457.                         local detected = ns.getNumberTargets()
  1458.                         local shown = detected
  1459.  
  1460.                         if tMin > 0 then
  1461.                             shown = max(tMin, shown)
  1462.                         end
  1463.                         if tMax > 0 then
  1464.                             shown = min(tMax, shown)
  1465.                         end
  1466.  
  1467.                         if tMax == 1 or shown > 1 then
  1468.                             local color = detected < shown and "|cFFFF0000" or ( shown < detected and "|cFF00C0FF" or "" )
  1469.                             b.Targets:SetText( color .. shown .. "|r")
  1470.                             b.targetShown = true
  1471.                         else
  1472.                             b.Targets:SetText(nil)
  1473.                             b.targetShown = false
  1474.                         end
  1475.                     elseif b.targetShown then
  1476.                         b.Targets:SetText(nil)
  1477.                     end
  1478.  
  1479.                     self.targetTimer = pulseTargets
  1480.                 end
  1481.  
  1482.                 local postTargets = debugprofilestop()
  1483.  
  1484.                 local b = self.Buttons[ 1 ]
  1485.  
  1486.                 self.delayTimer = self.delayTimer - elapsed
  1487.  
  1488.                 if b.ExactTime and ( self.delayTimer < 0 or self.NewRecommendations ) then
  1489.                     local a = b.Ability
  1490.  
  1491.                     local delay = b.ExactTime - now
  1492.                     local moment = 0
  1493.  
  1494.                     if delay > 0 then
  1495.                         local start, duration = 0, 0
  1496.  
  1497.                         if a.gcd ~= "off" then
  1498.                             start, duration = GetSpellCooldown( 61304 )
  1499.                             if start > 0 then moment = start + duration - now end
  1500.                         end
  1501.  
  1502.                         start, duration = select( 4, UnitCastingInfo( "player" ) )
  1503.                         if start and start > 0 then moment = max( ( start / 1000 ) + ( duration / 1000 ) - now, moment ) end
  1504.  
  1505.                         local rStart, rDuration = 0, 0
  1506.                         if a.item then
  1507.                             rStart, rDuration = C_Item.GetItemCooldown( a.item )
  1508.                         else
  1509.                             if a.cooldown > 0 or a.spendType ~= "runes" then
  1510.                                 rStart, rDuration = GetSpellCooldown( a.id )
  1511.                             end
  1512.                         end
  1513.                         if rStart > 0 then moment = max( moment, rStart + rDuration - now ) end
  1514.                     end
  1515.  
  1516.                     if conf.delays.type == "TEXT" then
  1517.                         if self.delayIconShown then
  1518.                             b.DelayIcon:Hide()
  1519.                             self.delayIconShown = false
  1520.                         end
  1521.  
  1522.                         if delay > moment + 0.05 then
  1523.                             b.DelayText:SetText( format( "%.1f", delay ) )
  1524.                             self.delayTextShown = true
  1525.                         else
  1526.                             b.DelayText:SetText( nil )
  1527.                             self.delayTextShown = false
  1528.                         end
  1529.  
  1530.                     elseif conf.delays.type == "ICON" then
  1531.                         if self.delayTextShown then
  1532.                             b.DelayText:SetText(nil)
  1533.                             self.delayTextShown = false
  1534.                         end
  1535.  
  1536.                         if delay > moment + 0.05 then
  1537.                             b.DelayIcon:Show()
  1538.                             b.DelayIcon:SetAlpha( self.alpha )
  1539.  
  1540.                             self.delayIconShown = true
  1541.  
  1542.                             if delay < 0.5 then
  1543.                                 b.DelayIcon:SetVertexColor( 0.0, 1.0, 0.0, 1.0 )
  1544.                             elseif delay < 1.5 then
  1545.                                 b.DelayIcon:SetVertexColor( 1.0, 1.0, 0.0, 1.0 )
  1546.                             else
  1547.                                 b.DelayIcon:SetVertexColor( 1.0, 0.0, 0.0, 1.0 )
  1548.                             end
  1549.                         else
  1550.                             b.DelayIcon:Hide()
  1551.                             b.delayIconShown = false
  1552.  
  1553.                         end
  1554.                     else
  1555.                         if self.delayTextShown then
  1556.                             b.DelayText:SetText( nil )
  1557.                             self.delayTextShown = false
  1558.                         end
  1559.                         if self.delayIconShown then
  1560.                             b.DelayIcon:Hide()
  1561.                             self.delayIconShown = false
  1562.                         end
  1563.                     end
  1564.  
  1565.                     self.delayTimer = pulseDelay
  1566.                 end
  1567.  
  1568.                 self.NewRecommendations = false
  1569.  
  1570.                 local finish = debugprofilestop()
  1571.  
  1572.                 if self.updateTime then
  1573.                     local newTime = self.updateTime * self.updateCount + ( finish - init )
  1574.                     self.updateCount = self.updateCount + 1
  1575.                     self.updateTime = newTime / self.updateCount
  1576.  
  1577.                     self.updateMax = max( self.updateMax, finish - init )
  1578.                     self.postAlpha = max( self.postAlpha, postAlpha - init )
  1579.                     self.postRecs = max( self.postRecs, postRecs - postAlpha )
  1580.                     self.postGlow = max( self.postGlow, postGlow - postRecs )
  1581.                     self.postRange = max( self.postRange, postRange - postGlow )
  1582.                     self.postFlash = max( self.postFlash, postFlash - postRange )
  1583.                     self.postTargets = max( self.postTargets, postTargets - postFlash )
  1584.                     self.postDelay = max( self.postDelay, finish - postTargets )
  1585.                 else
  1586.                     self.updateCount = 1
  1587.                     self.updateTime = finish - init
  1588.                     self.updateMax = finish - init
  1589.  
  1590.                     self.postAlpha = postAlpha - init
  1591.                     self.postRecs = postRecs - postAlpha
  1592.                     self.postGlow = postGlow - postRecs
  1593.                     self.postRange = postRange - postGlow
  1594.                     self.postFlash = postFlash - postRange
  1595.                     self.postTargets = postTargets - postFlash
  1596.                     self.postDelay = finish - postTargets
  1597.                 end
  1598.             end
  1599.         end
  1600.  
  1601.         Hekili:ProfileCPU( "HekiliDisplay" .. id .. ":OnUpdate", d.OnUpdate )
  1602.  
  1603.         function d:UpdateAlpha()
  1604.             if not self.Active then
  1605.                 self:SetAlpha( 0 )
  1606.                 self:Hide()
  1607.                 self.alpha = 0
  1608.                 return
  1609.             end
  1610.  
  1611.             local preAlpha = self.alpha or 0
  1612.             local newAlpha = CalculateAlpha( self.id )
  1613.  
  1614.             if preAlpha > 0 and newAlpha == 0 then
  1615.                 -- self:Deactivate()
  1616.                 self:SetAlpha( 0 )
  1617.                 self.alphaCheck = 0.5
  1618.             else
  1619.                 if preAlpha == 0 and newAlpha > 0 then
  1620.                     Hekili:ForceUpdate( "DISPLAY_ALPHA_CHANGED:" .. d.id .. ":" .. preAlpha .. ":" .. newAlpha .. ":" .. GetTime() )
  1621.                 end
  1622.                 self:SetAlpha( newAlpha )
  1623.                 self:Show()
  1624.             end
  1625.  
  1626.             self.alpha = newAlpha
  1627.         end
  1628.  
  1629.         function d:RefreshCooldowns( event )
  1630.             local gStart = GetSpellCooldown( 61304 )
  1631.             local cStart = ( select( 4, UnitCastingInfo( "player" ) ) or select( 4, UnitChannelInfo( "player" ) ) or 0 ) / 1000
  1632.  
  1633.             local now = GetTime()
  1634.             local conf = Hekili.DB.profile.displays[ self.id ]
  1635.  
  1636.             for i, rec in ipairs( self.Recommendations ) do
  1637.                 local button = self.Buttons[ i ]
  1638.  
  1639.                 if button.Action then
  1640.                     local cd = button.Cooldown
  1641.                     local ability = button.Ability
  1642.  
  1643.                     local start, duration, enabled, modRate = 0, 0, 1, 1
  1644.  
  1645.                     if ability.item then
  1646.                         start, duration, enabled, modRate = GetItemCooldown( ability.item )
  1647.                     elseif ability.key ~= state.empowerment.spell then
  1648.                         start, duration, enabled, modRate = GetSpellCooldown( ability.id )
  1649.                     end
  1650.  
  1651.                     if i == 1 and conf.delays.extend and rec.exact_time > max( now, start + duration ) then
  1652.                         start = ( start > 0 and start ) or ( cStart > 0 and cStart ) or ( gStart > 0 and gStart ) or max( state.gcd.lastStart, state.combat )
  1653.                         duration = rec.exact_time - start
  1654.  
  1655.                     elseif enabled and enabled == 0 then
  1656.                         start = 0
  1657.                         duration = 0
  1658.                         modRate = 1
  1659.                     end
  1660.  
  1661.                     if cd.lastStart ~= start or cd.lastDuration ~= duration then
  1662.                         cd:SetCooldown( start, duration, modRate )
  1663.                         cd.lastStart = start
  1664.                         cd.lastDuration = duration
  1665.                     end
  1666.  
  1667.                     if i == 1 and ability.empowered and conf.empowerment.glow then
  1668.                         if state.empowerment.spell == ability.key and duration == 0 then
  1669.                             button.Empowerment:Show()
  1670.                         else
  1671.                             button.Empowerment:Hide()
  1672.                         end
  1673.                     end
  1674.                 end
  1675.             end
  1676.         end
  1677.  
  1678.         function d:OnEvent( event, ... )
  1679.             if not self.Recommendations then
  1680.                 return
  1681.             end
  1682.             local conf = Hekili.DB.profile.displays[ self.id ]
  1683.  
  1684.             local init = debugprofilestop()
  1685.  
  1686.             if event == "SPELL_ACTIVATION_OVERLAY_GLOW_SHOW" then
  1687.                 if conf.glow.enabled then
  1688.                     for i, b in ipairs( self.Buttons ) do
  1689.                         if i > 1 and not conf.glow.queued then
  1690.                             break
  1691.                         end
  1692.  
  1693.                         if not b.Action then
  1694.                             break
  1695.                         end
  1696.  
  1697.                         local a = b.Ability
  1698.  
  1699.                         if not b.glowing and a and a.id == ... then
  1700.                             b.glowColor = b.glowColor or {}
  1701.  
  1702.                             if conf.glow.coloring == "class" then
  1703.                                 b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = RAID_CLASS_COLORS[ class.file ]:GetRGBA()
  1704.                             elseif conf.glow.coloring == "custom" then
  1705.                                 b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = unpack(conf.glow.color)
  1706.                             else
  1707.                                 b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = 0.95, 0.95, 0.32, 1
  1708.                             end
  1709.  
  1710.                             if conf.glow.mode == "default" then
  1711.                                 Glower.ButtonGlow_Start( b, b.glowColor )
  1712.                                 b.glowStop = Glower.ButtonGlow_Stop
  1713.                             elseif conf.glow.mode == "autocast" then
  1714.                                 Glower.AutoCastGlow_Start( b, b.glowColor )
  1715.                                 b.glowStop = Glower.AutoCastGlow_Stop
  1716.                             elseif conf.glow.mode == "pixel" then
  1717.                                 Glower.PixelGlow_Start( b, b.glowColor )
  1718.                                 b.glowStop = Glower.PixelGlow_Stop
  1719.                             end
  1720.  
  1721.                             b.glowing = true
  1722.                         end
  1723.                     end
  1724.                 end
  1725.             elseif event == "SPELL_ACTIVATION_OVERLAY_GLOW_HIDE" then
  1726.                 if conf.glow.enabled then
  1727.                     for i, b in ipairs(self.Buttons) do
  1728.                         if i > 1 and not conf.glow.queued then
  1729.                             break
  1730.                         end
  1731.  
  1732.                         if not b.Action then
  1733.                             break
  1734.                         end
  1735.  
  1736.                         local a = b.Ability
  1737.  
  1738.                         if b.glowing and ( not a or a.id == ... ) then
  1739.                             b:glowStop()
  1740.                             b.glowing = false
  1741.                         end
  1742.                     end
  1743.                 end
  1744.             elseif kbEvents[ event ] then
  1745.                 self:UpdateKeybindings()
  1746.  
  1747.             elseif alphaUpdateEvents[ event ] then
  1748.                 if event == "CLIENT_SCENE_OPENED" then
  1749.                     if ... == 1 then -- Minigame.
  1750.                         Hekili.ClientScene = true
  1751.                     end
  1752.                 elseif event == "CLIENT_SCENE_CLOSED" then
  1753.                     Hekili.ClientScene = nil
  1754.                 end
  1755.  
  1756.                 self:UpdateAlpha()
  1757.  
  1758.             end
  1759.  
  1760.             if flashEvents[ event ] then
  1761.                 self.flashReady = false
  1762.                 C_Timer.After( 3, function()
  1763.                     self.flashReady = true
  1764.                 end )
  1765.             end
  1766.  
  1767.             if event == "CURRENT_SPELL_CAST_CHANGED" then
  1768.                 local b = self.Buttons[ 1 ]
  1769.  
  1770.                 if conf.glow.highlight then
  1771.                     local ability = b.Ability
  1772.                     local isItem, id = false, ability and ability.id
  1773.  
  1774.                     if id and id < 0 then
  1775.                         isItem = true
  1776.                         id = ability.item
  1777.                     end
  1778.  
  1779.                     local spellID = select( 9, UnitCastingInfo( "player" ) ) or select( 9, UnitChannelInfo( "player" ) )
  1780.  
  1781.                     if id and ( isItem and IsCurrentItem( id ) or IsCurrentSpell( id ) ) then --  and b.ExactTime > GetTime() then
  1782.                         b.Highlight:Show()
  1783.                     else
  1784.                         b.Highlight:Hide()
  1785.                     end
  1786.                 elseif b.Highlight:IsShown() then
  1787.                     b.Highlight:Hide()
  1788.                 end
  1789.             end
  1790.  
  1791.             local finish = debugprofilestop()
  1792.  
  1793.             if self.eventTime then
  1794.                 local newTime = self.eventTime * self.eventCount + finish - init
  1795.                 self.eventCount = self.eventCount + 1
  1796.                 self.eventTime = newTime / self.eventCount
  1797.  
  1798.                 if finish - init > self.eventMax then
  1799.                     self.eventMax = finish - init
  1800.                     self.eventMaxType = event
  1801.                 end
  1802.             else
  1803.                 self.eventCount = 1
  1804.                 self.eventTime = finish - init
  1805.                 self.eventMax = finish - init
  1806.                 self.eventMaxType = event
  1807.             end
  1808.         end
  1809.  
  1810.         Hekili:ProfileCPU( "HekiliDisplay" .. id .. ":OnEvent", d.OnEvent )
  1811.  
  1812.         function d:Activate()
  1813.             if not self.Active then
  1814.                 self.Active = true
  1815.  
  1816.                 self.Recommendations = self.Recommendations or ( ns.queue and ns.queue[ self.id ] )
  1817.                 self.NewRecommendations = true
  1818.  
  1819.                 self.alphaCheck = 0
  1820.                 self.auraTimer = 0
  1821.                 self.delayTimer = 0
  1822.                 self.flashTimer = 0
  1823.                 self.glowTimer = 0
  1824.                 self.rangeTimer = 0
  1825.                 self.recTimer = 0
  1826.                 self.refreshTimer = 0
  1827.                 self.targetTimer = 0
  1828.  
  1829.                 self.lastUpdate = 0
  1830.  
  1831.                 self:SetScript( "OnUpdate", self.OnUpdate )
  1832.                 self:SetScript( "OnEvent", self.OnEvent )
  1833.  
  1834.                 if not self.Initialized then
  1835.                     -- Update Cooldown Wheels.
  1836.                     -- self:RegisterEvent( "ACTIONBAR_UPDATE_USABLE" )
  1837.                     -- self:RegisterEvent( "ACTIONBAR_UPDATE_COOLDOWN" )
  1838.                     -- self:RegisterEvent( "SPELL_UPDATE_COOLDOWN" )
  1839.                     -- self:RegisterEvent( "SPELL_UPDATE_USABLE" )
  1840.  
  1841.                     -- Show/Hide Overlay Glows.
  1842.                     self:RegisterEvent( "SPELL_ACTIVATION_OVERLAY_GLOW_SHOW" )
  1843.                     self:RegisterEvent( "SPELL_ACTIVATION_OVERLAY_GLOW_HIDE" )
  1844.  
  1845.                     -- Recalculate Alpha/Visibility.
  1846.                     for e in pairs( alphaUpdateEvents ) do
  1847.                         self:RegisterEvent( e )
  1848.                     end
  1849.  
  1850.                     -- Recheck spell displays if spells have changed.
  1851.                     self:RegisterEvent( "SPELLS_CHANGED" )
  1852.                     self:RegisterEvent( "CURRENT_SPELL_CAST_CHANGED" )
  1853.  
  1854.                     -- Update keybindings.
  1855.                     for k in pairs( kbEvents ) do
  1856.                         self:RegisterEvent( k )
  1857.                     end
  1858.  
  1859.                     for k in pairs( flashEvents ) do
  1860.                         self:RegisterEvent( k )
  1861.                     end
  1862.  
  1863.                     self.Initialized = true
  1864.                 end
  1865.  
  1866.                 -- Hekili:ProcessHooks( self.id )
  1867.             end
  1868.         end
  1869.  
  1870.         function d:Deactivate()
  1871.             self.Active = false
  1872.  
  1873.             self:SetScript( "OnUpdate", nil )
  1874.             self:SetScript( "OnEvent", nil )
  1875.  
  1876.             for i, b in ipairs( self.Buttons ) do
  1877.                 b:Hide()
  1878.             end
  1879.         end
  1880.  
  1881.  
  1882.         function d:GetPerimeterButtons()
  1883.             local left, right, top, bottom
  1884.             local lPos, rPos, tPos, bPos
  1885.  
  1886.             for i = 1, self.numIcons do
  1887.                 local button = self.Buttons[ i ]
  1888.  
  1889.                 if i == 1 then
  1890.                     lPos = button:GetLeft()
  1891.                     rPos = button:GetRight()
  1892.                     tPos = button:GetTop()
  1893.                     bPos = button:GetBottom()
  1894.  
  1895.                     left = button
  1896.                     right = button
  1897.                     top = button
  1898.                     bottom = button
  1899.                 else
  1900.                     if button:GetLeft() < lPos then
  1901.                         lPos = button:GetLeft()
  1902.                         left = button
  1903.                     end
  1904.  
  1905.                     if button:GetRight() > rPos then
  1906.                         rPos = button:GetRight()
  1907.                         right = button
  1908.                     end
  1909.  
  1910.                     if button:GetTop() > tPos then
  1911.                         tPos = button:GetTop()
  1912.                         top = button
  1913.                     end
  1914.  
  1915.                     if button:GetBottom() < bPos then
  1916.                         bPos = button:GetBottom()
  1917.                         bottom = button
  1918.                     end
  1919.                 end
  1920.             end
  1921.  
  1922.             return left, right, top, bottom
  1923.         end
  1924.  
  1925.         -- function d:UpdatePerformance( now, used, newRecs )
  1926.             --[[
  1927.             if not InCombatLockdown() then
  1928.                 self.combatUpdates.last = 0
  1929.                 return
  1930.             elseif self.combatUpdates.last == 0 then
  1931.                 self.combatUpdates.last = now - used
  1932.             end
  1933.  
  1934.             if used == nil then return end
  1935.             -- used = used / 1000 -- ms to sec.
  1936.  
  1937.             if self.combatTime.samples == 0 then
  1938.                 self.combatTime.fastest = used
  1939.                 self.combatTime.slowest = used
  1940.                 self.combatTime.average = used
  1941.  
  1942.                 self.combatTime.samples = 1
  1943.             else
  1944.                 if used < self.combatTime.fastest then self.combatTime.fastest = used end
  1945.                 if used > self.combatTime.slowest then
  1946.                     self.combatTime.slowest = used
  1947.                 end
  1948.  
  1949.                 self.combatTime.average = ( ( self.combatTime.average * self.combatTime.samples ) + used ) / ( self.combatTime.samples + 1 )
  1950.                 self.combatTime.samples = self.combatTime.samples + 1
  1951.             end
  1952.  
  1953.             if self.combatUpdates.samples == 0 or self.combatUpdates.last == 0 then
  1954.                 if self.combatUpdates.last == 0 then
  1955.                     self.combatUpdates.last = now
  1956.                 else
  1957.                     local interval = now - self.combatUpdates.last
  1958.                     self.combatUpdates.last = now
  1959.  
  1960.                     self.combatUpdates.shortest = interval
  1961.                     self.combatUpdates.longest = interval
  1962.                     self.combatUpdates.average = interval
  1963.  
  1964.                     self.combatUpdates.samples = 1
  1965.                 end
  1966.             else
  1967.                 local interval = now - self.combatUpdates.last
  1968.                 self.combatUpdates.last = now
  1969.  
  1970.                 if interval < self.combatUpdates.shortest then
  1971.                     self.combatUpdates.shortest = interval
  1972.                     self.combatUpdates.shortEvents = nil
  1973.  
  1974.                     local e = 0
  1975.                     for k in pairs( self.eventsTriggered ) do
  1976.                         if e == 0 then self.combatUpdates.shortEvents = k; e = 1
  1977.                         else self.combatUpdates.shortEvents = self.combatUpdates.shortEvents .. "|" .. k end
  1978.                     end
  1979.                 end
  1980.  
  1981.                 if interval > self.combatUpdates.longest  then
  1982.                     self.combatUpdates.longest = interval
  1983.                     self.combatUpdates.longEvents = nil
  1984.  
  1985.                     local e = 0
  1986.                     for k in pairs( self.eventsTriggered ) do
  1987.                         if e == 0 then self.combatUpdates.longEvents = k; e = 1
  1988.                         else self.combatUpdates.longEvents = self.combatUpdates.longEvents .. "|" .. k end
  1989.                     end
  1990.                 end
  1991.  
  1992.                 self.combatUpdates.average = ( ( self.combatUpdates.average * self.combatUpdates.samples ) + interval ) / ( self.combatUpdates.samples + 1 )
  1993.                 self.combatUpdates.samples = self.combatUpdates.samples + 1
  1994.             end
  1995.  
  1996.             if self.id == "Primary" then
  1997.                 self.successEvents = self.successEvents or {}
  1998.                 self.failEvents = self.failEvents or {}
  1999.  
  2000.                 local events = newRecs and self.successEvents or self.failEvents
  2001.  
  2002.                 for k in pairs( self.eventsTriggered ) do
  2003.                     if events[ k ] then events[ k ] = events[ k ] + 1
  2004.                     else events[ k ] = 1 end
  2005.                 end
  2006.  
  2007.                 table.wipe( self.eventsTriggered )
  2008.             end ]]
  2009.         -- end
  2010.  
  2011.         ns.queue[id] = ns.queue[id] or {}
  2012.         d.Recommendations = ns.queue[id]
  2013.  
  2014.         ns.UI.Buttons[id] = ns.UI.Buttons[id] or {}
  2015.         d.Buttons = ns.UI.Buttons[id]
  2016.  
  2017.         for i = 1, 10 do
  2018.             d.Buttons[ i ] = self:CreateButton( id, i )
  2019.             d.Buttons[ i ]:Hide()
  2020.  
  2021.             if conf.enabled and self:IsDisplayActive( id ) and i <= conf.numIcons then
  2022.                 if d.Recommendations[ i ] and d.Recommendations[ i ].actionName then
  2023.                     d.Buttons[ i ]:Show()
  2024.                 end
  2025.             end
  2026.  
  2027.             if MasqueGroup then
  2028.                 MasqueGroup:AddButton( d.Buttons[i], { Icon = d.Buttons[ i ].Texture, Cooldown = d.Buttons[ i ].Cooldown } )
  2029.             end
  2030.         end
  2031.  
  2032.         if d.forceElvUpdate then
  2033.             local E = _G.ElvUI and ElvUI[1]
  2034.             E:UpdateCooldownOverride( 'global' )
  2035.             d.forceElvUpdate = nil
  2036.         end
  2037.  
  2038.         if d.flashReady == nil then
  2039.             C_Timer.After( 3, function()
  2040.                 d.flashReady = true
  2041.             end )
  2042.         end
  2043.     end
  2044.  
  2045.  
  2046.     function Hekili:CreateCustomDisplay( id )
  2047.         local conf = rawget( self.DB.profile.displays, id )
  2048.         if not conf then return end
  2049.  
  2050.         dPool[ id ] = dPool[ id ] or CreateFrame( "Frame", "HekiliDisplay" .. id, UIParent )
  2051.         local d = dPool[ id ]
  2052.         self:ProfileFrame( "HekiliDisplay" .. id, d )
  2053.  
  2054.         d.id = id
  2055.  
  2056.         local scale = self:GetScale()
  2057.         local border = 2
  2058.  
  2059.         d:SetSize( scale * ( border + conf.primaryWidth ), scale * (border + conf.primaryHeight ) )
  2060.         d:SetPoint( "CENTER", nil, "CENTER", conf.x, conf.y )
  2061.         d:SetFrameStrata( "MEDIUM" )
  2062.         d:SetClampedToScreen( true )
  2063.         d:EnableMouse( false )
  2064.         d:SetMovable( true )
  2065.  
  2066.         d.Activate = HekiliDisplayPrimary.Activate
  2067.         d.Deactivate = HekiliDisplayPrimary.Deactivate
  2068.         d.RefreshCooldowns = HekiliDisplayPrimary.RefreshCooldowns
  2069.         d.UpdateAlpha = HekiliDisplayPrimary.UpdateAlpha
  2070.         d.UpdateKeybindings = HekiliDisplayPrimary.UpdateKeybindings
  2071.  
  2072.         ns.queue[id] = ns.queue[id] or {}
  2073.         d.Recommendations = ns.queue[id]
  2074.  
  2075.         ns.UI.Buttons[id] = ns.UI.Buttons[id] or {}
  2076.         d.Buttons = ns.UI.Buttons[id]
  2077.  
  2078.         for i = 1, 10 do
  2079.             d.Buttons[i] = self:CreateButton(id, i)
  2080.             d.Buttons[i]:Hide()
  2081.  
  2082.             if self.DB.profile.enabled and self:IsDisplayActive(id) and i <= conf.numIcons then
  2083.                 if d.Recommendations[i] and d.Recommendations[i].actionName then
  2084.                     d.Buttons[i]:Show()
  2085.                 end
  2086.             end
  2087.  
  2088.             if MasqueGroup then
  2089.                 MasqueGroup:AddButton(d.Buttons[i], {Icon = d.Buttons[i].Texture, Cooldown = d.Buttons[i].Cooldown})
  2090.             end
  2091.         end
  2092.     end
  2093.  
  2094.     local dispActive = {}
  2095.     local listActive = {}
  2096.     local actsActive = {}
  2097.  
  2098.     function Hekili:UpdateDisplayVisibility()
  2099.         local profile = self.DB.profile
  2100.         local displays = ns.UI.Displays
  2101.  
  2102.         for key in pairs( dispActive ) do
  2103.             dispActive[ key ] = nil
  2104.         end
  2105.  
  2106.         for list in pairs( listActive ) do
  2107.             listActive[ list ] = nil
  2108.         end
  2109.  
  2110.         for a in pairs( actsActive ) do
  2111.             actsActive[ a ] = nil
  2112.         end
  2113.  
  2114.         local specEnabled = GetSpecialization()
  2115.         specEnabled = specEnabled and GetSpecializationInfo( specEnabled )
  2116.  
  2117.         if class.specs[ specEnabled ] then
  2118.             specEnabled = specEnabled and rawget( profile.specs, specEnabled )
  2119.             specEnabled = specEnabled and rawget( specEnabled, "enabled" ) or false
  2120.         else
  2121.             specEnabled = false
  2122.         end
  2123.  
  2124.         if profile.enabled and specEnabled then
  2125.             for i, display in pairs( profile.displays ) do
  2126.                 if display.enabled then
  2127.                     if i == 'AOE' then
  2128.                         dispActive[i] = ( profile.toggles.mode.value == 'dual' or profile.toggles.mode.value == "reactive" ) and 1 or nil
  2129.                     elseif i == 'Interrupts' then
  2130.                         dispActive[i] = ( profile.toggles.interrupts.value and profile.toggles.interrupts.separate ) and 1 or nil
  2131.                     elseif i == 'Defensives' then
  2132.                         dispActive[i] = ( profile.toggles.defensives.value and profile.toggles.defensives.separate ) and 1 or nil
  2133.                     elseif i == 'Cooldowns' then
  2134.                         dispActive[i] = ( profile.toggles.cooldowns.value and profile.toggles.cooldowns.separate ) and 1 or nil
  2135.                     else
  2136.                         dispActive[i] = 1
  2137.                     end
  2138.  
  2139.                     if dispActive[i] == nil and self.Config then
  2140.                         dispActive[i] = 2
  2141.                     end
  2142.  
  2143.                     if dispActive[i] and displays[i] then
  2144.                         if not displays[i].Active then displays[i]:Activate() end
  2145.                         displays[i].NewRecommendations = true
  2146.                     end
  2147.                 else
  2148.                     if displays[i] and displays[i].Active then
  2149.                         displays[i]:Deactivate()
  2150.                     end
  2151.                 end
  2152.             end
  2153.  
  2154.             for packName, pack in pairs( profile.packs ) do
  2155.                 if pack.spec == 0 or pack.spec == state.spec.id then
  2156.                     for listName, list in pairs( pack.lists ) do
  2157.                         listActive[ packName .. ":" .. listName ] = true
  2158.  
  2159.                         -- NYI:  We can cache if abilities are disabled here as well to reduce checking in ProcessHooks.
  2160.                         for a, entry in ipairs( list ) do
  2161.                             if entry.enabled and entry.action then
  2162.                                 actsActive[ packName .. ":" .. listName .. ":" .. a ] = true
  2163.                             end
  2164.                         end
  2165.                     end
  2166.                 end
  2167.             end
  2168.         else
  2169.             for _, display in pairs( displays ) do
  2170.                 if display.Active then
  2171.                     display:Deactivate()
  2172.                 end
  2173.             end
  2174.         end
  2175.  
  2176.         for i, d in pairs( displays ) do
  2177.             d:UpdateAlpha()
  2178.         end
  2179.     end
  2180.  
  2181.     function Hekili:ReviewPacks()
  2182.         local profile = self.DB.profile
  2183.  
  2184.         for list in pairs( listActive ) do
  2185.             listActive[ list ] = nil
  2186.         end
  2187.  
  2188.         for a in pairs( actsActive ) do
  2189.             actsActive[ a ] = nil
  2190.         end
  2191.  
  2192.         for packName, pack in pairs( profile.packs ) do
  2193.             if pack.spec == 0 or pack.spec == state.spec.id then
  2194.                 for listName, list in pairs( pack.lists ) do
  2195.                     listActive[ packName .. ":" .. listName ] = true
  2196.  
  2197.                     -- NYI:  We can cache if abilities are disabled here as well to reduce checking in ProcessHooks.
  2198.                     for a, entry in ipairs( list ) do
  2199.                         if entry.enabled and entry.action and class.abilities[ entry.action ] then
  2200.                             actsActive[ packName .. ":" .. listName .. ":" .. a ] = true
  2201.                         end
  2202.                     end
  2203.                 end
  2204.             end
  2205.         end
  2206.     end
  2207.  
  2208.     function Hekili:IsDisplayActive( display, config )
  2209.         if config then
  2210.             return dispActive[ display ] == 1
  2211.         end
  2212.         return dispActive[display] ~= nil
  2213.     end
  2214.  
  2215.     function Hekili:IsListActive( pack, list )
  2216.         return pack == "UseItems" or ( listActive[ pack .. ":" .. list ] == true )
  2217.     end
  2218.  
  2219.     function Hekili:IsActionActive( pack, list, action )
  2220.         return pack == "UseItems" or ( actsActive[ pack .. ":" .. list .. ":" .. action ] == true )
  2221.     end
  2222.  
  2223.     function Hekili:DumpActionActive()
  2224.         DevTools_Dump( actsActive )
  2225.     end
  2226.  
  2227.  
  2228.     -- Separate the recommendations engine from each display.
  2229.     Hekili.Engine = CreateFrame( "Frame", "HekiliEngine" )
  2230.  
  2231.     Hekili.Engine.refreshTimer = 1
  2232.     Hekili.Engine.eventsTriggered = {}
  2233.  
  2234.     function Hekili.Engine:UpdatePerformance( wasted )
  2235.         -- Only track in combat.
  2236.         if not ( self.firstThreadCompleted and InCombatLockdown() ) then
  2237.             self.activeThreadTime = 0
  2238.             return
  2239.         end
  2240.  
  2241.         if self.firstThreadCompleted then
  2242.             local now = debugprofilestop()
  2243.             local timeSince = now - self.activeThreadStart
  2244.  
  2245.             self.lastUpdate = now
  2246.  
  2247.             if self.threadUpdates then
  2248.                 local updates = self.threadUpdates.updates
  2249.                 local total = updates + 1
  2250.  
  2251.                 if wasted then
  2252.                     -- Capture thrown away computation time due to forced resets.
  2253.                     self.threadUpdates.meanWasted    = ( self.threadUpdates.meanWasted    * updates + self.activeThreadTime   ) / total
  2254.                     self.threadUpdates.totalWasted   = ( self.threadUpdates.totalWasted   + self.activeThreadTime             )
  2255.  
  2256.                     if self.activeThreadTime   > self.threadUpdates.peakWasted    then self.threadUpdates.peakWasted    = self.activeThreadTime end
  2257.                 else
  2258.                     self.threadUpdates.meanClockTime = ( self.threadUpdates.meanClockTime * updates + timeSince               ) / total
  2259.                     self.threadUpdates.meanWorkTime  = ( self.threadUpdates.meanWorkTime  * updates + self.activeThreadTime   ) / total
  2260.                     self.threadUpdates.meanFrames    = ( self.threadUpdates.meanFrames    * updates + self.activeThreadFrames ) / total
  2261.  
  2262.                     if timeSince               > self.threadUpdates.peakClockTime then self.threadUpdates.peakClockTime = timeSince               end
  2263.                     if self.activeThreadTime   > self.threadUpdates.peakWorkTime  then self.threadUpdates.peakWorkTime  = self.activeThreadTime   end
  2264.                     if self.activeThreadFrames > self.threadUpdates.peakFrames    then self.threadUpdates.peakFrames    = self.activeThreadFrames end
  2265.  
  2266.                     self.threadUpdates.updates = total
  2267.                     self.threadUpdates.updatesPerSec = 1000 * total / ( now - self.threadUpdates.firstUpdate )
  2268.                 end
  2269.             else
  2270.                 self.threadUpdates = {
  2271.                     meanClockTime  = timeSince,
  2272.                     meanWorkTime   = self.activeThreadTime,
  2273.                     meanFrames     = self.activeThreadFrames or 1,
  2274.                     meanWasted     = 0,
  2275.  
  2276.                     firstUpdate    = now,
  2277.                     updates        = 1,
  2278.                     updatesPerSec  = 1000 / ( self.activeThreadTime > 0 and self.activeThreadTime or 1 ),
  2279.  
  2280.                     peakClockTime  = timeSince,
  2281.                     peakWorkTime   = self.activeThreadTime,
  2282.                     peakFrames     = self.activeThreadFrames or 1,
  2283.                     peakWasted     = 0,
  2284.  
  2285.                     totalWasted    = 0
  2286.                 }
  2287.             end
  2288.         end
  2289.  
  2290.         self.activeThreadTime = 0
  2291.     end
  2292.  
  2293.  
  2294.     local frameSpans = {}
  2295.  
  2296.     Hekili.Engine:SetScript( "OnUpdate", function( self, elapsed )
  2297.         if not self.activeThread then
  2298.             self.refreshTimer = self.refreshTimer + elapsed
  2299.             insert( frameSpans, elapsed )
  2300.         end
  2301.  
  2302.         if Hekili.DB.profile.enabled and not Hekili.Pause then
  2303.             self.refreshRate = self.refreshRate or 0.5
  2304.             self.combatRate = self.combatRate or 0.2
  2305.  
  2306.             local thread = self.activeThread
  2307.  
  2308.             local firstDisplay = nil
  2309.             local superUpdate = self.firstThreadCompleted and self.superUpdate
  2310.  
  2311.             -- If there's no thread, then see if we have a reason to update.
  2312.             if superUpdate or ( not thread and self.refreshTimer > ( self.criticalUpdate and self.combatRate or self.refreshRate ) ) then
  2313.                 if superUpdate and thread and coroutine.status( thread ) == "suspended" then
  2314.                     -- We're going to break the thread and start over from the current display in progress.
  2315.                     firstDisplay = state.display
  2316.                     self:UpdatePerformance( true )
  2317.                 end
  2318.  
  2319.                 self.criticalUpdate = false
  2320.                 self.superUpdate = false
  2321.                 self.refreshTimer = 0
  2322.  
  2323.                 self.activeThread = coroutine.create( Hekili.Update )
  2324.                 self.activeThreadTime = 0
  2325.                 self.activeThreadStart = debugprofilestop()
  2326.  
  2327.                 self.activeThreadFrames = 0
  2328.  
  2329.                 if not self.firstThreadCompleted then
  2330.                     Hekili.maxFrameTime = InCombatLockdown() and 10 or 25
  2331.                 else
  2332.                     if #frameSpans > 0 then
  2333.                         local averageSpan = 0
  2334.                         for _, span in ipairs( frameSpans ) do
  2335.                             averageSpan = averageSpan + span
  2336.                         end
  2337.                         averageSpan = 1000 * averageSpan / #frameSpans
  2338.                         wipe( frameSpans )
  2339.  
  2340.                         Hekili.maxFrameTime = Clamp( 0.6 * averageSpan, 3, 20 ) -- Dynamically adjust to 60% of (seemingly) average frame rate between updates.
  2341.                     else
  2342.                         Hekili.maxFrameTime = Hekili.maxFrameTime or 10
  2343.                     end
  2344.                 end
  2345.  
  2346.                 --[[
  2347.                 elseif Hekili:GetActiveSpecOption( "throttleTime" ) then
  2348.                     Hekili.maxFrameTime = Hekili:GetActiveSpecOption( "maxTime" ) or 15
  2349.                 else
  2350.                     Hekili.maxFrameTime = 15
  2351.                 end ]]
  2352.  
  2353.                 thread = self.activeThread
  2354.             end
  2355.  
  2356.             -- If there's a thread, process for up to user preferred limits.
  2357.             if thread and coroutine.status( thread ) == "suspended" then
  2358.                 self.activeThreadFrames = self.activeThreadFrames + 1
  2359.                 Hekili.activeFrameStart = debugprofilestop()
  2360.  
  2361.                 local ok, err = coroutine.resume( thread, firstDisplay )
  2362.  
  2363.                 if not ok then
  2364.                     err = err .. "\n\n" .. debugstack( thread )
  2365.                     Hekili:Error( "Update: " .. err )
  2366.  
  2367.                     if Hekili.ActiveDebug then
  2368.                         Hekili:Debug( format( "Recommendation thread terminated due to error: %s", err and err:gsub( "%%", "%%%%" ) or "Unknown" ) )
  2369.                         Hekili:SaveDebugSnapshot( self.id )
  2370.                         Hekili.ActiveDebug = nil
  2371.                     end
  2372.  
  2373.                     pcall( error, err )
  2374.                 end
  2375.  
  2376.                 self.activeThreadTime = self.activeThreadTime + debugprofilestop() - Hekili.activeFrameStart
  2377.  
  2378.                 if coroutine.status( thread ) == "dead" or err then
  2379.                     self.activeThread = nil
  2380.  
  2381.                     if Hekili:GetActiveSpecOption( "throttleRefresh" ) then
  2382.                         self.refreshRate = Hekili:GetActiveSpecOption( "regularRefresh" )
  2383.                         self.combatRate = Hekili:GetActiveSpecOption( "combatRefresh" )
  2384.                     else
  2385.                         self.refreshRate = 0.5
  2386.                         self.combatRate = 0.2
  2387.                     end
  2388.  
  2389.                     if ok then
  2390.                         self.firstThreadCompleted = true
  2391.                         self:UpdatePerformance()
  2392.                     end
  2393.                 end
  2394.  
  2395.                 if ok and err == "AutoSnapshot" then
  2396.                     Hekili:MakeSnapshot( true )
  2397.                 end
  2398.             end
  2399.         end
  2400.     end )
  2401.     Hekili:ProfileFrame( "HekiliEngine", Hekili.Engine )
  2402.  
  2403.  
  2404.     function HekiliEngine:IsThreadActive()
  2405.         return self.activeThread and coroutine.status( self.activeThread ) == "suspended"
  2406.     end
  2407.  
  2408.  
  2409.     function Hekili:ForceUpdate( event, super )
  2410.         self.Engine.criticalUpdate = true
  2411.         if super then
  2412.             self.Engine.superUpdate = true
  2413.         end
  2414.         if self.Engine.firstForce == 0 then self.Engine.firstForce = GetTime() end
  2415.  
  2416.         if event then
  2417.             self.Engine.eventsTriggered[ event ] = true
  2418.         end
  2419.     end
  2420.  
  2421.  
  2422.     local LSM = LibStub("LibSharedMedia-3.0", true)
  2423.  
  2424.     function Hekili:CreateButton( dispID, id )
  2425.         local d = dPool[ dispID ]
  2426.         if not d then
  2427.             return
  2428.         end
  2429.  
  2430.         local conf = rawget( self.DB.profile.displays, dispID )
  2431.         if not conf then return end
  2432.  
  2433.         ns.queue[ dispID ][ id ] = ns.queue[ dispID ][ id ] or {}
  2434.  
  2435.         local bName = "Hekili_" .. dispID .. "_B" .. id
  2436.         local b = d.Buttons[ id ] or CreateFrame( "Button", bName, d )
  2437.  
  2438.         Hekili:ProfileFrame( bName, b )
  2439.  
  2440.         b.display = dispID
  2441.         b.index = id
  2442.  
  2443.         local scale = self:GetScale()
  2444.  
  2445.         local borderOffset = 0
  2446.  
  2447.         if conf.border.enabled and conf.border.fit then
  2448.             borderOffset = 2
  2449.         end
  2450.  
  2451.         if id == 1 then
  2452.             b:SetHeight( scale * ( ( conf.primaryHeight or 50 ) - borderOffset ) )
  2453.             b:SetWidth( scale * ( ( conf.primaryWidth or 50 ) - borderOffset  ) )
  2454.         else
  2455.             b:SetHeight( scale * ( ( conf.queue.height or 30 ) - borderOffset  ) )
  2456.             b:SetWidth( scale * ( ( conf.queue.width or 50 ) - borderOffset  ) )
  2457.         end
  2458.  
  2459.         -- Texture
  2460.         if not b.Texture then
  2461.             b.Texture = b:CreateTexture( nil, "ARTWORK" )
  2462.             b.Texture:SetTexture( "Interface\\ICONS\\Spell_Nature_BloodLust" )
  2463.             b.Texture:SetAllPoints( b )
  2464.         end
  2465.  
  2466.         b.texCoords = b.texCoords or {}
  2467.         local zoom = 1 - ( ( conf.zoom or 0) / 200 )
  2468.  
  2469.         if conf.keepAspectRatio then
  2470.             local biggest = id == 1 and max( conf.primaryHeight, conf.primaryWidth ) or max( conf.queue.height, conf.queue.width )
  2471.             local height = 0.5 * zoom * ( id == 1 and conf.primaryHeight or conf.queue.height ) / biggest
  2472.             local width = 0.5 * zoom * ( id == 1 and conf.primaryWidth or conf.queue.width ) / biggest
  2473.  
  2474.             b.texCoords[1] = 0.5 - width
  2475.             b.texCoords[2] = 0.5 + width
  2476.             b.texCoords[3] = 0.5 - height
  2477.             b.texCoords[4] = 0.5 + height
  2478.  
  2479.             b.Texture:SetTexCoord( unpack( b.texCoords ) )
  2480.         else
  2481.             local zoom = zoom / 2
  2482.  
  2483.             b.texCoords[1] = 0.5 - zoom
  2484.             b.texCoords[2] = 0.5 + zoom
  2485.             b.texCoords[3] = 0.5 - zoom
  2486.             b.texCoords[4] = 0.5 + zoom
  2487.  
  2488.             b.Texture:SetTexCoord( unpack( b.texCoords ) )
  2489.         end
  2490.  
  2491.  
  2492.         -- Initialize glow/noop if button has not yet been glowed.
  2493.         b.glowing = b.glowing or false
  2494.         b.glowStop = b.glowStop or function () end
  2495.  
  2496.  
  2497.         -- Indicator Icons.
  2498.         b.Icon = b.Icon or b:CreateTexture( nil, "OVERLAY" )
  2499.         b.Icon: SetSize( max( 10, b:GetWidth() / 3 ), max( 10, b:GetHeight() / 3 ) )
  2500.  
  2501.         if conf.keepAspectRatio and b.Icon:GetHeight() ~= b.Icon:GetWidth() then
  2502.             local biggest = max( b.Icon:GetHeight(), b.Icon:GetWidth() )
  2503.             local height = 0.5 * b.Icon:GetHeight() / biggest
  2504.             local width = 0.5 * b.Icon:GetWidth() / biggest
  2505.  
  2506.             b.Icon:SetTexCoord( 0.5 - width, 0.5 + width, 0.5 - height, 0.5 + height )
  2507.         else
  2508.             b.Icon:SetTexCoord( 0, 1, 0, 1 )
  2509.         end
  2510.  
  2511.         local iconAnchor = conf.indicators.anchor or "RIGHT"
  2512.  
  2513.         b.Icon:ClearAllPoints()
  2514.         b.Icon:SetPoint( iconAnchor, b, iconAnchor, conf.indicators.x or 0, conf.indicators.y or 0 )
  2515.         b.Icon:Hide()
  2516.  
  2517.  
  2518.         -- Caption Text.
  2519.         b.Caption = b.Caption or b:CreateFontString( bName .. "_Caption", "OVERLAY" )
  2520.  
  2521.         local captionFont = conf.captions.font or conf.font
  2522.         b.Caption:SetFont( LSM:Fetch("font", captionFont), conf.captions.fontSize or 12, conf.captions.fontStyle or "OUTLINE" )
  2523.  
  2524.         local capAnchor = conf.captions.anchor or "BOTTOM"
  2525.         b.Caption:ClearAllPoints()
  2526.         b.Caption:SetPoint( capAnchor, b, capAnchor, conf.captions.x or 0, conf.captions.y or 0 )
  2527.         b.Caption:SetHeight( b:GetHeight() / 2 )
  2528.         b.Caption:SetJustifyV( capAnchor:match("RIGHT") and "RIGHT" or ( capAnchor:match( "LEFT" ) and "LEFT" or "MIDDLE" ) )
  2529.         b.Caption:SetJustifyH( conf.captions.align or "CENTER" )
  2530.         b.Caption:SetTextColor( unpack( conf.captions.color ) )
  2531.         b.Caption:SetWordWrap( false )
  2532.  
  2533.         local capText = b.Caption:GetText()
  2534.         b.Caption:SetText( nil )
  2535.         b.Caption:SetText( capText )
  2536.  
  2537.  
  2538.         -- Keybinding Text
  2539.         b.Keybinding = b.Keybinding or b:CreateFontString(bName .. "_KB", "OVERLAY")
  2540.  
  2541.         local queued = id > 1 and conf.keybindings.separateQueueStyle
  2542.         local kbFont = queued and conf.keybindings.queuedFont or conf.keybindings.font or conf.font
  2543.  
  2544.         b.Keybinding:SetFont( LSM:Fetch("font", kbFont), queued and conf.keybindings.queuedFontSize or conf.keybindings.fontSize or 12, queued and conf.keybindings.queuedFontStyle or conf.keybindings.fontStyle or "OUTLINE" )
  2545.  
  2546.         local kbAnchor = conf.keybindings.anchor or "TOPRIGHT"
  2547.         b.Keybinding:ClearAllPoints()
  2548.         b.Keybinding:SetPoint( kbAnchor, b, kbAnchor, conf.keybindings.x or 0, conf.keybindings.y or 0 )
  2549.         b.Keybinding:SetHeight( b:GetHeight() / 2 )
  2550.         b.Keybinding:SetJustifyH( kbAnchor:match("RIGHT") and "RIGHT" or ( kbAnchor:match( "LEFT" ) and "LEFT" or "CENTER" ) )
  2551.         b.Keybinding:SetJustifyV( kbAnchor:match("TOP") and "TOP" or ( kbAnchor:match( "BOTTOM" ) and "BOTTOM" or "MIDDLE" ) )
  2552.         b.Keybinding:SetTextColor( unpack( queued and conf.keybindings.queuedColor or conf.keybindings.color ) )
  2553.         b.Keybinding:SetWordWrap( false )
  2554.  
  2555.         local kbText = b.Keybinding:GetText()
  2556.         b.Keybinding:SetText( nil )
  2557.         b.Keybinding:SetText( kbText )
  2558.  
  2559.         -- Cooldown Wheel
  2560.         if not b.Cooldown then
  2561.             b.Cooldown = CreateFrame( "Cooldown", bName .. "_Cooldown", b, "CooldownFrameTemplate" )
  2562.             if id == 1 then b.Cooldown:HookScript( "OnCooldownDone", function( self )
  2563.                     if b.Ability and b.Ability.empowered and conf.empowerment.glow and state.empowerment.spell == b.Ability.key then
  2564.                         b.Empowerment:Show()
  2565.                     else
  2566.                         b.Empowerment:Hide()
  2567.                     end
  2568.                 end )
  2569.             end
  2570.         end
  2571.         b.Cooldown:ClearAllPoints()
  2572.         b.Cooldown:SetAllPoints( b )
  2573.         b.Cooldown:SetFrameStrata( b:GetFrameStrata() )
  2574.         b.Cooldown:SetFrameLevel( b:GetFrameLevel() + 1 )
  2575.         b.Cooldown:SetDrawBling( false )
  2576.         b.Cooldown:SetDrawEdge( false )
  2577.  
  2578.         b.Cooldown.noCooldownCount = conf.hideOmniCC
  2579.  
  2580.         if _G["ElvUI"] and not b.isRegisteredCooldown and ( ( id == 1 and conf.elvuiCooldown ) or ( id > 1 and conf.queue.elvuiCooldown ) ) then
  2581.             local E = unpack( ElvUI )
  2582.  
  2583.             local cd = b.Cooldown.CooldownSettings or {}
  2584.             cd.font = E.Libs.LSM:Fetch( "font", E.db.cooldown.fonts.font )
  2585.             cd.fontSize = E.db.cooldown.fonts.fontSize
  2586.             cd.fontOutline = E.db.cooldown.fonts.fontOutline
  2587.             b.Cooldown.CooldownSettings = cd
  2588.  
  2589.             E:RegisterCooldown( b.Cooldown )
  2590.             d.forceElvUpdate = true
  2591.         end
  2592.  
  2593.         -- Backdrop (for borders)
  2594.         b.Backdrop = b.Backdrop or Mixin( CreateFrame("Frame", bName .. "_Backdrop", b ), BackdropTemplateMixin )
  2595.         b.Backdrop:ClearAllPoints()
  2596.         b.Backdrop:SetWidth( b:GetWidth() + ( conf.border.thickness and ( 2 * conf.border.thickness ) or 2 ) )
  2597.         b.Backdrop:SetHeight( b:GetHeight() + ( conf.border.thickness and ( 2 * conf.border.thickness ) or 2 ) )
  2598.  
  2599.         local framelevel = b:GetFrameLevel()
  2600.         if framelevel > 0 then
  2601.             -- b.Backdrop:SetFrameStrata( "MEDIUM" )
  2602.             b.Backdrop:SetFrameLevel( framelevel - 1 )
  2603.         else
  2604.             local lowerStrata = frameStratas[ b:GetFrameStrata() ]
  2605.             lowerStrata = frameStratas[ lowerStrata - 1 ]
  2606.             b.Backdrop:SetFrameStrata( lowerStrata or "LOW" )
  2607.         end
  2608.  
  2609.         b.Backdrop:SetPoint( "CENTER", b, "CENTER" )
  2610.         b.Backdrop:Hide()
  2611.  
  2612.         if conf.border.enabled then
  2613.             b.Backdrop:SetBackdrop( {
  2614.                 bgFile = nil,
  2615.                 edgeFile = "Interface\\Buttons\\WHITE8X8",
  2616.                 tile = false,
  2617.                 tileSize = 0,
  2618.                 edgeSize = conf.border.thickness or 1,
  2619.                 insets = { left = -1, right = -1, top = -1, bottom = -1 }
  2620.             } )
  2621.             if conf.border.coloring == 'custom' then
  2622.                 b.Backdrop:SetBackdropBorderColor( unpack( conf.border.color ) )
  2623.             else
  2624.                 b.Backdrop:SetBackdropBorderColor( RAID_CLASS_COLORS[ class.file ]:GetRGBA() )
  2625.             end
  2626.             b.Backdrop:Show()
  2627.         else
  2628.             b.Backdrop:SetBackdrop( nil )
  2629.             b.Backdrop:SetBackdropColor( 0, 0, 0, 0 )
  2630.             b.Backdrop:Hide()
  2631.         end
  2632.  
  2633.  
  2634.         -- Primary Icon Stuff
  2635.         if id == 1 then
  2636.             -- Anchoring stuff for the queue.
  2637.             b:ClearAllPoints()
  2638.             b:SetPoint( "CENTER", d, "CENTER" )
  2639.  
  2640.             -- Highlight
  2641.             if not b.Highlight then
  2642.                 b.Highlight = b:CreateTexture( nil, "OVERLAY" )
  2643.                 b.Highlight:SetTexture( "Interface\\Buttons\\ButtonHilight-Square" )
  2644.                 b.Highlight:SetAllPoints( b )
  2645.                 b.Highlight:SetBlendMode( "ADD" )
  2646.                 b.Highlight:Hide()
  2647.             end
  2648.  
  2649.             -- Target Counter
  2650.             b.Targets = b.Targets or b:CreateFontString( bName .. "_Targets", "OVERLAY" )
  2651.  
  2652.             local tarFont = conf.targets.font or conf.font
  2653.             b.Targets:SetFont( LSM:Fetch( "font", tarFont ), conf.targets.fontSize or 12, conf.targets.fontStyle or "OUTLINE" )
  2654.  
  2655.             local tarAnchor = conf.targets.anchor or "BOTTOM"
  2656.             b.Targets:ClearAllPoints()
  2657.             b.Targets:SetPoint( tarAnchor, b, tarAnchor, conf.targets.x or 0, conf.targets.y or 0 )
  2658.             b.Targets:SetHeight( b:GetHeight() / 2 )
  2659.             b.Targets:SetJustifyH( tarAnchor:match("RIGHT") and "RIGHT" or ( tarAnchor:match( "LEFT" ) and "LEFT" or "CENTER" ) )
  2660.             b.Targets:SetJustifyV( tarAnchor:match("TOP") and "TOP" or ( tarAnchor:match( "BOTTOM" ) and "BOTTOM" or "MIDDLE" ) )
  2661.             b.Targets:SetTextColor( unpack( conf.targets.color ) )
  2662.             b.Targets:SetWordWrap( false )
  2663.  
  2664.             local tText = b.Targets:GetText()
  2665.             b.Targets:SetText( nil )
  2666.             b.Targets:SetText( tText )
  2667.  
  2668.             -- Aura Counter
  2669.             -- Disabled for Now
  2670.             --[[ b.Auras = b.Auras or b:CreateFontString(bName .. "_Auras", "OVERLAY")
  2671.  
  2672.             local auraFont = conf.auraFont or (ElvUI and "PT Sans Narrow" or "Arial Narrow")
  2673.             b.Auras:SetFont(LSM:Fetch("font", auraFont), conf.auraFontSize or 12, conf.auraFontStyle or "OUTLINE")
  2674.             b.Auras:SetSize(b:GetWidth(), b:GetHeight() / 2)
  2675.  
  2676.             local auraAnchor = conf.auraAnchor or "BOTTOM"
  2677.             b.Auras:ClearAllPoints()
  2678.             b.Auras:SetPoint(auraAnchor, b, auraAnchor, conf.xOffsetAuras or 0, conf.yOffsetAuras or 0)
  2679.  
  2680.             b.Auras:SetJustifyH(
  2681.                 auraAnchor:match("RIGHT") and "RIGHT" or (auraAnchor:match("LEFT") and "LEFT" or "CENTER")
  2682.             )
  2683.             b.Auras:SetJustifyV(
  2684.                 auraAnchor:match("TOP") and "TOP" or (auraAnchor:match("BOTTOM") and "BOTTOM" or "MIDDLE")
  2685.             )
  2686.             b.Auras:SetTextColor(1, 1, 1, 1) ]]
  2687.  
  2688.  
  2689.             -- Delay Counter
  2690.             b.DelayText = b.DelayText or b:CreateFontString( bName .. "_DelayText", "OVERLAY" )
  2691.  
  2692.             local delayFont = conf.delays.font or conf.font
  2693.             b.DelayText:SetFont( LSM:Fetch("font", delayFont), conf.delays.fontSize or 12, conf.delays.fontStyle or "OUTLINE" )
  2694.  
  2695.             local delayAnchor = conf.delays.anchor or "TOPLEFT"
  2696.             b.DelayText:ClearAllPoints()
  2697.             b.DelayText:SetPoint( delayAnchor, b, delayAnchor, conf.delays.x, conf.delays.y or 0 )
  2698.             b.DelayText:SetHeight( b:GetHeight() / 2 )
  2699.  
  2700.             b.DelayText:SetJustifyH( delayAnchor:match( "RIGHT" ) and "RIGHT" or ( delayAnchor:match( "LEFT" ) and "LEFT" or "CENTER") )
  2701.             b.DelayText:SetJustifyV( delayAnchor:match( "TOP" ) and "TOP" or ( delayAnchor:match( "BOTTOM" ) and "BOTTOM" or "MIDDLE") )
  2702.             b.DelayText:SetTextColor( unpack( conf.delays.color ) )
  2703.  
  2704.             local dText = b.DelayText:GetText()
  2705.             b.DelayText:SetText( nil )
  2706.             b.DelayText:SetText( dText )
  2707.  
  2708.  
  2709.             -- Delay Icon
  2710.             b.DelayIcon = b.DelayIcon or b:CreateTexture( bName .. "_DelayIcon", "OVERLAY" )
  2711.             b.DelayIcon:SetSize( min( 20, max( 10, b:GetSize() / 3 ) ), min( 20, max( 10, b:GetSize() / 3 ) ) )
  2712.             b.DelayIcon:SetTexture( "Interface\\FriendsFrame\\StatusIcon-Online" )
  2713.             b.DelayIcon:SetDesaturated( true )
  2714.             b.DelayIcon:SetVertexColor( 1, 0, 0, 1 )
  2715.  
  2716.             b.DelayIcon:ClearAllPoints()
  2717.             b.DelayIcon:SetPoint( delayAnchor, b, delayAnchor, conf.delays.x or 0, conf.delays.y or 0 )
  2718.             b.DelayIcon:Hide()
  2719.  
  2720.             -- Empowerment
  2721.             b.Empowerment = b.Empowerment or b:CreateTexture( bName .. "_Empower", "OVERLAY" )
  2722.             b.Empowerment:SetAtlas( "bags-glow-artifact" )
  2723.             b.Empowerment:SetVertexColor( 1, 1, 1, 1 )
  2724.  
  2725.             b.Empowerment:ClearAllPoints()
  2726.             b.Empowerment:SetPoint( "TOPLEFT", b, "TOPLEFT", -1, 1 )
  2727.             b.Empowerment:SetPoint( "BOTTOMRIGHT", b, "BOTTOMRIGHT", 1, -1 )
  2728.             b.Empowerment:Hide()
  2729.  
  2730.             -- Overlay (for Pause)
  2731.             b.Overlay = b.Overlay or b:CreateTexture( nil, "OVERLAY" )
  2732.             b.Overlay:SetAllPoints( b )
  2733.             b.Overlay:SetAtlas( "creditsscreen-assets-buttons-pause" )
  2734.             b.Overlay:SetVertexColor( 1, 1, 1, 1 )
  2735.             -- b.Overlay:SetTexCoord( unpack( b.texCoords ) )
  2736.             b.Overlay:Hide()
  2737.  
  2738.         elseif id == 2 then
  2739.             -- Anchoring for the remainder.
  2740.             local queueAnchor = conf.queue.anchor or "RIGHT"
  2741.             local qOffsetX = ( conf.queue.offsetX or 5 )
  2742.             local qOffsetY = ( conf.queue.offsetY or 0 )
  2743.  
  2744.             b:ClearAllPoints()
  2745.  
  2746.             if queueAnchor:sub( 1, 5 ) == "RIGHT" then
  2747.                 local dir, align = "RIGHT", queueAnchor:sub(6)
  2748.                 b:SetPoint( align .. getInverseDirection(dir), "Hekili_" .. dispID .. "_B1", align .. dir, ( borderOffset + qOffsetX ) * scale, qOffsetY * scale )
  2749.             elseif queueAnchor:sub( 1, 4 ) == "LEFT" then
  2750.                 local dir, align = "LEFT", queueAnchor:sub(5)
  2751.                 b:SetPoint( align .. getInverseDirection(dir), "Hekili_" .. dispID .. "_B1", align .. dir, -1 * ( borderOffset + qOffsetX ) * scale, qOffsetY * scale )
  2752.             elseif queueAnchor:sub( 1, 3)  == "TOP" then
  2753.                 local dir, align = "TOP", queueAnchor:sub(4)
  2754.                 b:SetPoint( getInverseDirection(dir) .. align, "Hekili_" .. dispID .. "_B1", dir .. align, 0, ( borderOffset + qOffsetY ) * scale )
  2755.             else -- BOTTOM
  2756.                 local dir, align = "BOTTOM", queueAnchor:sub(7)
  2757.                 b:SetPoint( getInverseDirection(dir) .. align, "Hekili_" .. dispID .. "_B1", dir .. align, 0, -1 * ( borderOffset + qOffsetY ) * scale )
  2758.             end
  2759.         else
  2760.             local queueDirection = conf.queue.direction or "RIGHT"
  2761.             local btnSpacing = borderOffset + ( conf.queue.spacing or 5 )
  2762.  
  2763.             b:ClearAllPoints()
  2764.  
  2765.             if queueDirection == "RIGHT" then
  2766.                 b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, btnSpacing * scale, 0 )
  2767.             elseif queueDirection == "LEFT" then
  2768.                 b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, -1 * btnSpacing * scale, 0 )
  2769.             elseif queueDirection == "TOP" then
  2770.                 b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, 0, btnSpacing * scale )
  2771.             else -- BOTTOM
  2772.                 b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, 0, -1 * btnSpacing * scale )
  2773.             end
  2774.         end
  2775.  
  2776.  
  2777.         -- Caption Text.
  2778.         b.EmpowerLevel = b.EmpowerLevel or b:CreateFontString( bName .. "_EmpowerLevel", "OVERLAY" )
  2779.  
  2780.         local empowerFont = conf.empowerment.font or conf.font
  2781.         b.EmpowerLevel:SetFont( LSM:Fetch("font", empowerFont), conf.empowerment.fontSize or 12, conf.empowerment.fontStyle or "OUTLINE" )
  2782.  
  2783.         local empAnchor = conf.empowerment.anchor or "CENTER"
  2784.         b.EmpowerLevel:ClearAllPoints()
  2785.         b.EmpowerLevel:SetPoint( empAnchor, b, empAnchor, conf.empowerment.x or 0, conf.empowerment.y or 0 )
  2786.         -- b.EmpowerLevel:SetHeight( b:GetHeight() * 0.6 )
  2787.         b.EmpowerLevel:SetJustifyV( empAnchor:match("RIGHT") and "RIGHT" or ( empAnchor:match( "LEFT" ) and "LEFT" or "MIDDLE" ) )
  2788.         b.EmpowerLevel:SetJustifyH( conf.empowerment.align or "CENTER" )
  2789.         b.EmpowerLevel:SetTextColor( unpack( conf.empowerment.color ) )
  2790.         b.EmpowerLevel:SetWordWrap( false )
  2791.  
  2792.         local empText = b.EmpowerLevel:GetText()
  2793.         b.EmpowerLevel:SetText( nil )
  2794.         b.EmpowerLevel:SetText( empText )
  2795.  
  2796.         -- Mover Stuff.
  2797.         b:SetScript( "OnMouseDown", Button_OnMouseDown )
  2798.         b:SetScript( "OnMouseUp", Button_OnMouseUp )
  2799.  
  2800.         b:SetScript( "OnEnter", function( self )
  2801.             local H = Hekili
  2802.  
  2803.             --[[ if H.Config then
  2804.                 Tooltip:SetOwner( self, "ANCHOR_TOPRIGHT" )
  2805.                 Tooltip:SetBackdropColor( 0, 0, 0, 0.8 )
  2806.  
  2807.                 Tooltip:SetText( "Hekili: " .. dispID  )
  2808.                 Tooltip:AddLine( "Left-click and hold to move.", 1, 1, 1 )
  2809.                 Tooltip:Show()
  2810.                 self:SetMovable( true )
  2811.  
  2812.             else ]]
  2813.             if ( H.Pause and d.HasRecommendations and b.Recommendation ) then
  2814.                 H:ShowDiagnosticTooltip( b.Recommendation )
  2815.             end
  2816.         end )
  2817.  
  2818.         b:SetScript( "OnLeave", function(self)
  2819.             HekiliTooltip:Hide()
  2820.         end )
  2821.  
  2822.         Hekili:ProfileFrame( bName, b )
  2823.  
  2824.         b:EnableMouse( false )
  2825.         b:SetMovable( false )
  2826.  
  2827.         return b
  2828.     end
  2829. end
  2830.  
  2831. -- Builds and maintains the visible UI elements.
  2832. -- Buttons (as frames) are never deleted, but should get reused effectively.
  2833.  
  2834. local builtIns = {
  2835.     "Primary", "AOE", "Cooldowns", "Interrupts", "Defensives"
  2836. }
  2837.  
  2838. function Hekili:BuildUI()
  2839.     if not Masque then
  2840.         Masque = LibStub( "Masque", true )
  2841.  
  2842.         if Masque then
  2843.             Masque:Register( addon, MasqueUpdate, self )
  2844.             MasqueGroup = Masque:Group( addon )
  2845.         end
  2846.     end
  2847.  
  2848.     local LSM = LibStub( "LibSharedMedia-3.0" )
  2849.  
  2850.     ns.UI.Keyhandler = ns.UI.Keyhandler or CreateFrame( "Button", "Hekili_Keyhandler", UIParent )
  2851.     ns.UI.Keyhandler:RegisterForClicks( "AnyDown" )
  2852.     ns.UI.Keyhandler:SetScript( "OnClick", function( self, button, down )
  2853.         Hekili:FireToggle( button )
  2854.     end )
  2855.     Hekili:ProfileFrame( "KeyhandlerFrame", ns.UI.Keyhandler )
  2856.  
  2857.     local scaleFactor = self:GetScale()
  2858.     local mouseInteract = self.Pause
  2859.  
  2860.     -- Notification Panel
  2861.     local notif = self.DB.profile.notifications
  2862.  
  2863.     local f = ns.UI.Notification or CreateFrame( "Frame", "HekiliNotification", UIParent )
  2864.     Hekili:ProfileFrame( "HekiliNotification", f )
  2865.  
  2866.     f:SetSize( notif.width * scaleFactor, notif.height * scaleFactor )
  2867.     f:SetClampedToScreen( true )
  2868.     f:ClearAllPoints()
  2869.     f:SetPoint("CENTER", nil, "CENTER", notif.x, notif.y )
  2870.  
  2871.     f.Text = f.Text or f:CreateFontString( "HekiliNotificationText", "OVERLAY" )
  2872.     f.Text:SetAllPoints( f )
  2873.     f.Text:SetFont( LSM:Fetch( "font", notif.font ), notif.fontSize * scaleFactor, notif.fontStyle )
  2874.     f.Text:SetJustifyV("MIDDLE")
  2875.     f.Text:SetJustifyH("CENTER")
  2876.     f.Text:SetTextColor(1, 1, 1, 1)
  2877.  
  2878.     if not notif.enabled then f:Hide()
  2879.     else f.Text:SetText(nil); f:Show() end
  2880.  
  2881.     ns.UI.Notification = f
  2882.     -- End Notification Panel
  2883.  
  2884.     -- Displays
  2885.     for disp in pairs( self.DB.profile.displays ) do
  2886.         self:CreateDisplay( disp )
  2887.     end
  2888.  
  2889.     --if Hekili.Config then ns.StartConfiguration() end
  2890.     if MasqueGroup then
  2891.         MasqueGroup:ReSkin()
  2892.     end
  2893.  
  2894.     -- Check for a display that has been removed.
  2895.     for display, buttons in ipairs(ns.UI.Buttons) do
  2896.         if not Hekili.DB.profile.displays[display] then
  2897.             for i, _ in ipairs(buttons) do
  2898.                 buttons[i]:Hide()
  2899.             end
  2900.         end
  2901.     end
  2902.  
  2903.     if Hekili.Config then
  2904.         ns.StartConfiguration(true)
  2905.     end
  2906. end
  2907.  
  2908. local T = ns.lib.Format.Tokens
  2909. local SyntaxColors = {}
  2910.  
  2911. function ns.primeTooltipColors()
  2912.     T = ns.lib.Format.Tokens
  2913.     --- Assigns a color to multiple tokens at once.
  2914.     local function Color(Code, ...)
  2915.         for Index = 1, select("#", ...) do
  2916.             SyntaxColors[select(Index, ...)] = Code
  2917.         end
  2918.     end
  2919.     Color( "|cffB266FF", T.KEYWORD ) -- Reserved Words
  2920.  
  2921.     Color( "|cffffffff", T.LEFTCURLY, T.RIGHTCURLY, T.LEFTBRACKET, T.RIGHTBRACKET, T.LEFTPAREN, T.RIGHTPAREN )
  2922.  
  2923.     Color( "|cffFF66FF", T.UNKNOWN,
  2924.         T.ADD,
  2925.         T.SUBTRACT,
  2926.         T.MULTIPLY,
  2927.         T.DIVIDE,
  2928.         T.POWER,
  2929.         T.MODULUS,
  2930.         T.CONCAT,
  2931.         T.VARARG,
  2932.         T.ASSIGNMENT,
  2933.         T.PERIOD,
  2934.         T.COMMA,
  2935.         T.SEMICOLON,
  2936.         T.COLON,
  2937.         T.SIZE,
  2938.         T.EQUALITY,
  2939.         T.NOTEQUAL,
  2940.         T.LT,
  2941.         T.LTE,
  2942.         T.GT,
  2943.         T.GTE )
  2944.  
  2945.     Color( "|cFFB2FF66", multiUnpack(ns.keys, ns.attr) )
  2946.  
  2947.     Color( "|cffFFFF00", T.NUMBER )
  2948.     Color( "|cff888888", T.STRING, T.STRING_LONG )
  2949.     Color( "|cff55cc55", T.COMMENT_SHORT, T.COMMENT_LONG )
  2950.     Color( "|cff55ddcc", -- Minimal standard Lua functions
  2951.         "assert",
  2952.         "error",
  2953.         "ipairs",
  2954.         "next",
  2955.         "pairs",
  2956.         "pcall",
  2957.         "print",
  2958.         "select",
  2959.         "tonumber",
  2960.         "tostring",
  2961.         "type",
  2962.         "unpack",
  2963.         -- Libraries
  2964.         "bit",
  2965.         "coroutine",
  2966.         "math",
  2967.         "string",
  2968.         "table"
  2969.     )
  2970.     Color( "|cffddaaff", -- Some of WoW's aliases for standard Lua functions
  2971.         -- math
  2972.         "abs",
  2973.         "ceil",
  2974.         "floor",
  2975.         "max",
  2976.         "min",
  2977.         -- string
  2978.         "format",
  2979.         "gsub",
  2980.         "strbyte",
  2981.         "strchar",
  2982.         "strconcat",
  2983.         "strfind",
  2984.         "strjoin",
  2985.         "strlower",
  2986.         "strmatch",
  2987.         "strrep",
  2988.         "strrev",
  2989.         "strsplit",
  2990.         "strsub",
  2991.         "strtrim",
  2992.         "strupper",
  2993.         "tostringall",
  2994.         -- table
  2995.         "sort",
  2996.         "tinsert",
  2997.         "tremove",
  2998.         "wipe" )
  2999. end
  3000.  
  3001.  
  3002. local SpaceLeft = {"(%()"}
  3003. local SpaceRight = {"(%))"}
  3004. local DoubleSpace = {"(!=)", "(~=)", "(>=*)", "(<=*)", "(&)", "(||)", "(+)", "(*)", "(-)", "(/)"}
  3005.  
  3006.  
  3007. local function Format(Code)
  3008.     for Index = 1, #SpaceLeft do
  3009.         Code = Code:gsub("%s-" .. SpaceLeft[Index] .. "%s-", " %1")
  3010.     end
  3011.  
  3012.     for Index = 1, #SpaceRight do
  3013.         Code = Code:gsub("%s-" .. SpaceRight[Index] .. "%s-", "%1 ")
  3014.     end
  3015.  
  3016.     for Index = 1, #DoubleSpace do
  3017.         Code = Code:gsub("%s-" .. DoubleSpace[Index] .. "%s-", " %1 ")
  3018.     end
  3019.  
  3020.     Code = Code:gsub("([^<>~!])(=+)", "%1 %2 ")
  3021.     Code = Code:gsub("%s+", " "):trim()
  3022.     return Code
  3023. end
  3024.  
  3025.  
  3026. local key_cache = setmetatable( {}, {
  3027.     __index = function( t, k )
  3028.         t[k] = k:gsub( "(%S+)%[(%d+)]", "%1.%2" )
  3029.         return t[k]
  3030.     end
  3031. })
  3032.  
  3033.  
  3034. function Hekili:ShowDiagnosticTooltip( q )
  3035.     if not q.actionName or not class.abilities[ q.actionName ].name then return end
  3036.  
  3037.     local tt = HekiliTooltip
  3038.     local fmt = ns.lib.Format
  3039.  
  3040.     tt:SetOwner( UIParent, "ANCHOR_CURSOR" )
  3041.     tt:SetText( class.abilities[ q.actionName ].name )
  3042.     tt:AddDoubleLine( q.listName .. " #" .. q.action, "+" .. ns.formatValue(round(q.time or 0, 2)), 1, 1, 1, 1, 1, 1 )
  3043.  
  3044.     if q.resources and q.resources[q.resource_type] then
  3045.         tt:AddDoubleLine(q.resource_type, ns.formatValue(q.resources[q.resource_type]), 1, 1, 1, 1, 1, 1)
  3046.     end
  3047.  
  3048.     if q.HookHeader or (q.HookScript and q.HookScript ~= "") then
  3049.         if q.HookHeader then
  3050.             tt:AddLine(" ")
  3051.             tt:AddLine(q.HookHeader)
  3052.         else
  3053.             tt:AddLine(" ")
  3054.             tt:AddLine("Hook Criteria")
  3055.         end
  3056.  
  3057.         if q.HookScript and q.HookScript ~= "" then
  3058.             local Text = Format(q.HookScript)
  3059.             tt:AddLine(fmt.FormatCode(Text, 0, SyntaxColors), 1, 1, 1, 1)
  3060.         end
  3061.  
  3062.         if q.HookElements then
  3063.             local applied = false
  3064.             for k, v in orderedPairs(q.HookElements) do
  3065.                 if not applied then
  3066.                     tt:AddLine(" ")
  3067.                     tt:AddLine("Values")
  3068.                     applied = true
  3069.                 end
  3070.                 if not key_cache[k]:find( "safebool" ) and not key_cache[k]:find( "safenum" ) and not key_cache[k]:find( "ceil" ) and not key_cache[k]:find( "floor" ) then
  3071.                     tt:AddDoubleLine( key_cache[ k ], ns.formatValue(v), 1, 1, 1, 1, 1, 1)
  3072.                 end
  3073.             end
  3074.         end
  3075.     end
  3076.  
  3077.     if q.ReadyScript and q.ReadyScript ~= "" then
  3078.         tt:AddLine(" ")
  3079.         tt:AddLine("Time Script")
  3080.  
  3081.         tt:AddLine(fmt.FormatCode(q.ReadyScript, 0, SyntaxColors), 1, 1, 1, 1)
  3082.  
  3083.         if q.ReadyElements then
  3084.             tt:AddLine("Values")
  3085.             for k, v in orderedPairs(q.ReadyElements) do
  3086.                 if not key_cache[k]:find( "safebool" ) and not key_cache[k]:find( "safenum" ) and not key_cache[k]:find( "ceil" ) and not key_cache[k]:find( "floor" ) then
  3087.                     tt:AddDoubleLine( key_cache[ k ], ns.formatValue(v), 1, 1, 1, 1, 1, 1)
  3088.                 end
  3089.             end
  3090.         end
  3091.     end
  3092.  
  3093.     if q.ActScript and q.ActScript ~= "" then
  3094.         tt:AddLine(" ")
  3095.         tt:AddLine("Action Criteria")
  3096.  
  3097.         tt:AddLine(fmt.FormatCode(q.ActScript, 0, SyntaxColors), 1, 1, 1, 1)
  3098.  
  3099.         if q.ActElements then
  3100.             tt:AddLine(" ")
  3101.             tt:AddLine("Values")
  3102.             for k, v in orderedPairs(q.ActElements) do
  3103.                 if not key_cache[k]:find( "safebool" ) and not key_cache[k]:find( "safenum" ) and not key_cache[k]:find( "ceil" ) and not key_cache[k]:find( "floor" ) then
  3104.                     tt:AddDoubleLine( key_cache[ k ], ns.formatValue(v), 1, 1, 1, 1, 1, 1)
  3105.                 end
  3106.             end
  3107.         end
  3108.     end
  3109.  
  3110.     if q.pack and q.listName and q.action then
  3111.         local entry = rawget( self.DB.profile.packs, q.pack )
  3112.         entry = entry and entry.lists[ q.listName ]
  3113.         entry = entry and entry[ q.action ]
  3114.  
  3115.         if entry and entry.description and entry.description:len() > 0 then
  3116.             tt:AddLine( " " )
  3117.             tt:AddLine( entry.description, 0, 0.7, 1, true )
  3118.         end
  3119.     end
  3120.  
  3121.     tt:SetMinimumWidth( 400 )
  3122.     tt:Show()
  3123. end
  3124.  
  3125. function Hekili:SaveCoordinates()
  3126.     for i in pairs(Hekili.DB.profile.displays) do
  3127.         local display = ns.UI.Displays[i]
  3128.         if display then
  3129.             local rel, x, y = select( 3, display:GetPoint() )
  3130.  
  3131.             self.DB.profile.displays[i].rel = "CENTER"
  3132.             self.DB.profile.displays[i].x = x
  3133.             self.DB.profile.displays[i].y = y
  3134.         end
  3135.     end
  3136.  
  3137.     self.DB.profile.notifications.x, self.DB.profile.notifications.y = select( 4, HekiliNotification:GetPoint() )
  3138. end
  3139.  
Advertisement
Add Comment
Please, Sign In to add comment