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:
-- 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.
|