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:
-- Create the secure group panel
-- Use SecureGroupPetPanel for pet
local raidPanel = Scorpio.Secure.SecureGroupPanel("MyRaidPanel")
-- Set the element type to Scorpio.Secure.UnitFrame, that's the template for unit frame
raidPanel.ElementType = Scorpio.Secure.UnitFrame
-- The unit frames will be created with name MyUnitFrameUnit1 ~ MyUnitFrameUnit20
raidPanel.ElementPrefix = "MyUnitFrameUnit"
raidPanel:SetPoint("CENTER", 100, 0)
raidPanel.ColumnCount = 4 -- The column count
raidPanel.RowCount = 5 -- The row count
raidPanel.ElementWidth = 80 -- The unit frame width
raidPanel.ElementHeight = 48 -- The unit frame height
raidPanel.Orientation = "VERTICAL" -- The unit frame genereation orientation
raidPanel.LeftToRight = true -- Layout the unit frame from left to right
raidPanel.TopToBottom = true -- Layout the unit frame from the top to bottom
raidPanel.HSpacing = 2 -- The horizontal spacing of unit frames
raidPanel.VSpacing = 2 -- The vertical spacing of unit frames
-- The settings provided by SecureGroupHeaderTemplate
raidPanel.ShowRaid = true -- Show in raid
raidPanel.ShowParty = true -- Show in party
raidPanel.ShowSolo = true -- Show in solo
raidPanel.ShowPlayer = true -- Show the player
raidPanel.ShowDeadOnly = false -- Only show dead player
raidPanel.GroupFilter = { 1, 2, 3, 4, 5, 6, 7, 8 }
raidPanel.ClassFilter = { "WARRIOR", "DEATHKNIGHT", "PALADIN", "MONK", "PRIEST", "SHAMAN", "DRUID", "ROGUE", "MAGE", "WARLOCK", "HUNTER", "DEMONHUNTER" }
raidPanel.RoleFilter = { "MAINTANK", "MAINASSIST", "TANK", "HEALER", "DAMAGER", "NONE"}
raidPanel.GroupBy = "NONE" -- "NONE", "GROUP", "CLASS", "ROLE", "ASSIGNEDROLE"
raidPanel.SortBy = "INDEX" -- "INDEX", "NAME"
-- The script handler when unit frames added to the raid panel
-- Used to init the unit frames
function raidPanel:OnElementAdd(unitframe)
-- You can bind a event handler to receive the unit changes
unitframe.OnUnitRefresh = function(self, unit)
print(self:GetName(), unit)
end
end
-- Init enough unit frames to make sure those unit frames
-- generated at the start of the game, although the
-- unit frame can be generated based on the raid changes
-- but if you entering the game during combat, it'll be
-- delayed out of combat, so just create the max count
-- unit frames here
raidPanel.Count = raidPanel.MaxCount
-- Indicators, You can use your own Indicators mechanism
-- On those unit frames, which can be accessed by MyUnitFrameUnit1
-- Or raidPanel.Elements[1~N]
Scorpio.UI.Style[raidPanel.Elements[1]] = {
NameLabel = {
location = { { point = "CENTER" } },
textColor = Scorpio.Wow.UnitConditionColor(), -- change the text color based on the health condition
},
}
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:
-- Our group header to contains the unit frames
local groupHeader = CreateFrame("Frame", "MyGroupHeader", UIParent, "SecureFrameTemplate")
-- A shadow group header to send the unit changes
local shadowHeader = CreateFrame("Frame", "MyGroupHeaderShadowHeader", groupHeader, "SecureGroupHeaderTemplate, SecureHandlerStateTemplate")
-- Some helper methods
function shadowHeader:Execute(body) return SecureHandlerExecute(self, body) end
function shadowHeader:SetFrameRef(label, refFrame) return SecureHandlerSetFrameRef(self, label, refFrame) end
shadowHeader:Execute([=[
Manager = self
UnitFrames = newtable()
ShadowFrames = newtable()
ShadowUnitMap = newtable()
-- The secure snippet only used when entering game during combat
refreshUnitChange = [[
local unit = self:GetAttribute("unit")
local frame = self:GetAttribute("UnitFrame")
if frame then
self:GetAttribute("Manager"):RunAttribute("onShadowUnitChanged", self:GetID(), value)
end
]]
-- The secure snippet to trigger the delay re-layout
Manager:SetAttribute("onShadowUnitChanged", [[
local id, unit = ...
if ShadowUnitMap[id] ~= unit then
ShadowUnitMap[id] = unit
Manager:RunAttribute("DelayLayout")
end
]])
-- The init config to shadow unit frames
Manager:SetAttribute("template", "SecureHandlerAttributeTemplate")
Manager:SetAttribute("strictFiltering", true)
Manager:SetAttribute("initialConfigFunction", [=[
tinsert(ShadowFrames, self)
self:SetWidth(0)
self:SetHeight(0)
self:SetID(#ShadowFrames)
self:SetAttribute("Manager", Manager)
-- Binding
local frame = UnitFrames[#ShadowFrames]
if frame then self:SetAttribute("UnitFrame", frame) end
-- Only for the entering game combat
-- refreshUnitChange won't fire when the unit is set to nil
self:SetAttribute("refreshUnitChange", refreshUnitChange)
-- Call the method to generate the real unit frames
Manager:CallMethod("UpdateUnitCount", #ShadowFrames)
]=])
]=])
-- The secure delay mechanism to avoid too many re-layout in the same time
shadowHeader:SetAttribute("DelayLayout", [[
-- Reset the timer
Manager:SetAttribute("state-timer", "reset")
]])
shadowHeader:SetAttribute("_onstate-timer", [=[
if newstate ~= "reset" then
-- Refresh the units and re-layout the frames
local count = #ShadowFrames
for i = 1, count do
local frm = UnitFrames[i]
if not frm then return end
-- Refersh the unit of the unit frames
frm:SetAttribute("unit", ShadowFrames[i]:GetAttribute("unit"))
-- Re-layout the unit frames, skip the details
frm:ClearAllPoints()
frm:SetPoint(...)
end
for i = count + 1, #UnitFrames do
UnitFrames[i]:SetAttribute("unit", nil)
UnitFrames[i]:Hide()
end
end
]=])
-- The state driver are only used as a delay timer, so no matter how the conditon defined
shadowHeader:RegisterStateDriver("timer", "[pet]pet;nopet;")
function shadowHeader:UpdateUnitCount(count)
-- Keep run this code out of combat, I skipped the InCombatLockDown check here
-- Init the panel
for i = (self.__InitedCount or 0) + 1, count do
-- Get the shadow unit frame and use _onattributechanged instead of the refreshUnitChange
local child = self:GetAttribute("child" .. i)
child:SetAttribute("refreshUnitChange", nil) -- only used for the entering game combat
child:SetAttribute("_onattributechanged", [[
if name == "unit" then
if type(value) == "string" then
value = strlower(value)
else
value = nil
end
if self:GetAttribute("UnitFrame") then
self:GetAttribute("Manager"):RunAttribute("onShadowUnitChanged", self:GetID(), value)
end
end
]])
end
self.__InitedCount = count
groupHeader:InitWithCount(count)
end
function groupHeader:InitWithCount(count)
for i = (self.__UnitCount or 0) + 1, count do
local unitFrame = CreateFrame("Frame", "MyGroupHeaderUnitFrame" .. i, self, "SecureUnitButtonTemplate, SecureHandlerAttributeTemplate")
shadowHeader:SetFrameRef("UnitFrame", unitFrame)
shadowHeader:Execute([=[
local frame = Manager:GetFrameRef("UnitFrame")
tinsert(UnitFrames, frame)
-- Binding
local shadow = ShadowFrames[#UnitFrames]
if shadow then
shadow:SetAttribute("UnitFrame", frame)
frame:SetAttribute("unit", shadow:GetAttribute("unit"))
end
]=])
end
groupHeader.__UnitCount = math.max(groupHeader.__UnitCount or 0, count)
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.