WoWInterface

WoWInterface (https://www.wowinterface.com/forums/index.php)
-   Dev Tools (https://www.wowinterface.com/forums/forumdisplay.php?f=41)
-   -   Solution to re-layout raid panel during combat (https://www.wowinterface.com/forums/showthread.php?t=58697)

kurapica.igas 04-15-21 09:30 PM

Solution to re-layout raid panel during combat
 
There are two type raid panel, one that modify the CompactRaidFrame but it has many limits, one created by SecureGroupHeaderTemplate, many authors think that can't be re-layout during combat, popular addons like Grid2, Vuhdo also have that problem.

Although I had solved that a long times ago, but people just love popular. I'll share two solutions, you can check the second solution if you have secure snippet experiences and don't want use another Lib.

I. Use the Scorpio Lib.

Lua Code:
  1. -- Create the secure group panel
  2. -- Use SecureGroupPetPanel for pet
  3. local raidPanel = Scorpio.Secure.SecureGroupPanel("MyRaidPanel")
  4.  
  5. -- Set the element type to Scorpio.Secure.UnitFrame, that's the template for unit frame
  6. raidPanel.ElementType = Scorpio.Secure.UnitFrame
  7.  
  8. -- The unit frames will be created with name MyUnitFrameUnit1 ~ MyUnitFrameUnit20
  9. raidPanel.ElementPrefix = "MyUnitFrameUnit"
  10. raidPanel:SetPoint("CENTER", 100, 0)
  11.  
  12. raidPanel.ColumnCount = 4             -- The column count
  13. raidPanel.RowCount = 5                -- The row count
  14. raidPanel.ElementWidth = 80           -- The unit frame width
  15. raidPanel.ElementHeight = 48          -- The unit frame height
  16. raidPanel.Orientation = "VERTICAL"    -- The unit frame genereation orientation
  17. raidPanel.LeftToRight = true          -- Layout the unit frame from left to right
  18. raidPanel.TopToBottom = true          -- Layout the unit frame from the top to bottom
  19. raidPanel.HSpacing = 2                -- The horizontal spacing of unit frames
  20. raidPanel.VSpacing = 2                -- The vertical spacing of unit frames
  21.  
  22. -- The settings provided by SecureGroupHeaderTemplate
  23. raidPanel.ShowRaid = true             -- Show in raid
  24. raidPanel.ShowParty = true            -- Show in party
  25. raidPanel.ShowSolo = true             -- Show in solo
  26. raidPanel.ShowPlayer = true           -- Show the player
  27. raidPanel.ShowDeadOnly = false        -- Only show dead player
  28.  
  29. raidPanel.GroupFilter = { 1, 2, 3, 4, 5, 6, 7, 8 }
  30. raidPanel.ClassFilter = { "WARRIOR", "DEATHKNIGHT", "PALADIN", "MONK", "PRIEST", "SHAMAN", "DRUID", "ROGUE", "MAGE", "WARLOCK", "HUNTER", "DEMONHUNTER" }
  31. raidPanel.RoleFilter = { "MAINTANK", "MAINASSIST", "TANK", "HEALER", "DAMAGER", "NONE"}
  32. raidPanel.GroupBy = "NONE"            -- "NONE", "GROUP", "CLASS", "ROLE", "ASSIGNEDROLE"
  33. raidPanel.SortBy = "INDEX"            -- "INDEX", "NAME"
  34.  
  35. -- The script handler when unit frames added to the raid panel
  36. -- Used to init the unit frames
  37. function raidPanel:OnElementAdd(unitframe)
  38.  
  39.     -- You can bind a event handler to receive the unit changes
  40.     unitframe.OnUnitRefresh = function(self, unit)
  41.         print(self:GetName(), unit)
  42.     end
  43. end
  44.  
  45. -- Init enough unit frames to make sure those unit frames
  46. -- generated at the start of the game, although the
  47. -- unit frame can be generated based on the raid changes
  48. -- but if you entering the game during combat, it'll be
  49. -- delayed out of combat, so just create the max count
  50. -- unit frames here
  51. raidPanel.Count = raidPanel.MaxCount
  52.  
  53. -- Indicators, You can use your own Indicators mechanism
  54. -- On those unit frames, which can be accessed by MyUnitFrameUnit1
  55. -- Or raidPanel.Elements[1~N]
  56. Scorpio.UI.Style[raidPanel.Elements[1]] = {    
  57.     NameLabel = {
  58.         location = { { point = "CENTER" } },
  59.         textColor = Scorpio.Wow.UnitConditionColor(), --  change the text color based on the health condition
  60.     },
  61. }

You can leave the details to the Scorpio, and enjoy the further updatings. BTW. if you have interesting about the Scorpio's Style system(which all indicators can be defined in the style table), you can check Scoriop Docs and AshToAsh for an example.



II. The implementing based on the secure mechanism.

I assume the author read this part have enough secure snippet experences. There are two parts to make that happend.

i. Use a shadow panel created by SecureGroupHeaderTemplate to notify the group roster update in the secure environments.

ii. The group roster update may occurs many times during the same time, we need a secure delay mechanism to make sure only do one time re-layout.

Here is an example code.

Lua Code:
  1. -- Our group header to contains the unit frames
  2. local groupHeader = CreateFrame("Frame", "MyGroupHeader", UIParent, "SecureFrameTemplate")
  3.  
  4. -- A shadow group header to send the unit changes
  5. local shadowHeader = CreateFrame("Frame", "MyGroupHeaderShadowHeader", groupHeader, "SecureGroupHeaderTemplate, SecureHandlerStateTemplate")
  6.  
  7. -- Some helper methods
  8. function shadowHeader:Execute(body) return SecureHandlerExecute(self, body) end
  9. function shadowHeader:SetFrameRef(label, refFrame) return SecureHandlerSetFrameRef(self, label, refFrame) end
  10.  
  11. shadowHeader:Execute([=[
  12.     Manager             = self
  13.     UnitFrames          = newtable()
  14.     ShadowFrames        = newtable()
  15.     ShadowUnitMap       = newtable()
  16.  
  17.     -- The secure snippet only used when entering game during combat
  18.     refreshUnitChange   = [[
  19.         local unit      = self:GetAttribute("unit")
  20.         local frame     = self:GetAttribute("UnitFrame")
  21.  
  22.         if frame then
  23.             self:GetAttribute("Manager"):RunAttribute("onShadowUnitChanged", self:GetID(), value)
  24.         end
  25.     ]]
  26.  
  27.     -- The secure snippet to trigger the delay re-layout
  28.     Manager:SetAttribute("onShadowUnitChanged", [[
  29.         local id, unit  = ...
  30.         if ShadowUnitMap[id] ~= unit then
  31.             ShadowUnitMap[id] = unit
  32.             Manager:RunAttribute("DelayLayout")
  33.         end
  34.     ]])
  35.  
  36.     -- The init config to shadow unit frames
  37.     Manager:SetAttribute("template", "SecureHandlerAttributeTemplate")
  38.     Manager:SetAttribute("strictFiltering", true)
  39.  
  40.     Manager:SetAttribute("initialConfigFunction",  [=[
  41.         tinsert(ShadowFrames, self)
  42.  
  43.         self:SetWidth(0)
  44.         self:SetHeight(0)
  45.         self:SetID(#ShadowFrames)
  46.         self:SetAttribute("Manager", Manager)
  47.  
  48.         -- Binding
  49.         local frame     = UnitFrames[#ShadowFrames]
  50.         if frame then self:SetAttribute("UnitFrame", frame) end
  51.  
  52.         -- Only for the entering game combat
  53.         -- refreshUnitChange won't fire when the unit is set to nil
  54.         self:SetAttribute("refreshUnitChange", refreshUnitChange)
  55.  
  56.         -- Call the method to generate the real unit frames
  57.         Manager:CallMethod("UpdateUnitCount", #ShadowFrames)
  58.     ]=])
  59. ]=])
  60.  
  61. -- The secure delay mechanism to avoid too many re-layout in the same time
  62. shadowHeader:SetAttribute("DelayLayout", [[
  63.     -- Reset the timer
  64.     Manager:SetAttribute("state-timer", "reset")
  65. ]])
  66.  
  67. shadowHeader:SetAttribute("_onstate-timer", [=[
  68.     if newstate ~= "reset" then
  69.         -- Refresh the units and re-layout the frames
  70.         local count     = #ShadowFrames
  71.         for i = 1, count do
  72.             local frm   = UnitFrames[i]
  73.             if not frm then return end
  74.  
  75.             -- Refersh the unit of the unit frames
  76.             frm:SetAttribute("unit", ShadowFrames[i]:GetAttribute("unit"))
  77.  
  78.             -- Re-layout the unit frames, skip the details
  79.             frm:ClearAllPoints()
  80.             frm:SetPoint(...)  
  81.         end
  82.  
  83.         for i = count + 1, #UnitFrames do
  84.             UnitFrames[i]:SetAttribute("unit", nil)
  85.  
  86.             UnitFrames[i]:Hide()
  87.         end
  88.     end
  89. ]=])
  90.  
  91. -- The state driver are only used as a delay timer, so no matter how the conditon defined
  92. shadowHeader:RegisterStateDriver("timer", "[pet]pet;nopet;")
  93.  
  94. function shadowHeader:UpdateUnitCount(count)
  95.     -- Keep run this code out of combat, I skipped the InCombatLockDown check here
  96.     -- Init the panel
  97.     for i = (self.__InitedCount or 0) + 1, count do
  98.         -- Get the shadow unit frame and use _onattributechanged instead of the refreshUnitChange
  99.         local child     = self:GetAttribute("child" .. i)
  100.  
  101.         child:SetAttribute("refreshUnitChange", nil)    -- only used for the entering game combat
  102.         child:SetAttribute("_onattributechanged", [[
  103.             if name == "unit" then
  104.                 if type(value) == "string" then
  105.                     value = strlower(value)
  106.                 else
  107.                     value = nil
  108.                 end
  109.  
  110.                 if self:GetAttribute("UnitFrame") then
  111.                     self:GetAttribute("Manager"):RunAttribute("onShadowUnitChanged", self:GetID(), value)
  112.                 end
  113.             end
  114.         ]])
  115.     end
  116.     self.__InitedCount  = count
  117.  
  118.     groupHeader:InitWithCount(count)
  119. end
  120.  
  121. function groupHeader:InitWithCount(count)
  122.     for i = (self.__UnitCount or 0) + 1, count do
  123.         local unitFrame = CreateFrame("Frame", "MyGroupHeaderUnitFrame" .. i, self, "SecureUnitButtonTemplate, SecureHandlerAttributeTemplate")
  124.  
  125.         shadowHeader:SetFrameRef("UnitFrame", unitFrame)
  126.         shadowHeader:Execute([=[
  127.             local frame = Manager:GetFrameRef("UnitFrame")
  128.             tinsert(UnitFrames, frame)
  129.  
  130.             -- Binding
  131.             local shadow = ShadowFrames[#UnitFrames]
  132.  
  133.             if shadow then
  134.                 shadow:SetAttribute("UnitFrame", frame)
  135.                 frame:SetAttribute("unit", shadow:GetAttribute("unit"))
  136.             end
  137.         ]=])
  138.     end
  139.  
  140.     groupHeader.__UnitCount = math.max(groupHeader.__UnitCount or 0, count)
  141. end

This only describe the mechanism to re-layout the raid panels, if you need the real code, you may need check those files:

1. SecurePanel.lua, the secure panel which used to re-layout based on the visiblity of the elements.

2. SecureGroupPanel.lua, the secure group panel inherited the Secure Panel, and use a shadow group header to refresh the unit frames generated by it.

3. UnitFrame.lua, the unit frame definition also with a hover spell manage system, this file could be ignored.


All times are GMT -6. The time now is 08:22 PM.

vBulletin © 2021, Jelsoft Enterprises Ltd
© 2004 - 2020 MMOUI