-- This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. -- To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ -- or send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA. local MereHealingFrames, privateVars = ... MereHealingFrames.HealingPanelsLocked = false MereHealingFrames.HealingPanelLayout = { TopLeftX = 400, TopLeftY = 650, BorderX = 4, BorderY = 4, PanelGapX = 2, PanelGapY = 2, PanelColumns = 4, PanelRows = 5, PanelWidth = 100, PanelHeight = 60, Name = "Unnamed Healing Panel Layout", LayoutOffset = 0, RoleFilter = nil, GroupingFilter = nil, CallingFilter = nil, orderBy = "compactgroup", AnchorPoint = "TOPLEFT", ShowNameLabel = true, LabelHeight = 20, PanelSettings = nil, FillRowsThenColumns = false, FillRightToLeft = false, FillBottomToTop = false, -- implementation details Height = 250, Width = 400, RelayPanelsInGroup = nil, Frame = nil, Panels = nil, isSecure = false, specifierMapping = nil, configuringLayout = false, PendingLayoutChange = false, } local SavedVariables = { TopLeftX = true, TopLeftY = true, BorderX = true, BorderY = true, PanelGapX = true, PanelGapY = true, PanelColumns = true, PanelRows = true, PanelWidth = true, PanelHeight = true, Name = true, LayoutOffset = true, RoleFilter = true, CallingFilter = true, GroupingFilter = true, orderBy = true, AnchorPoint = true, ShowNameLabel = true, LabelHeight = true, PanelSettings = true, FillRowsThenColumns = true, FillRightToLeft = true, FillBottomToTop = true, } function MereHealingFrames.HealingPanelLayout:Save() local savedLayout = {} for key, value in pairs(SavedVariables) do savedLayout[key] = self[key] end return savedLayout end function MereHealingFrames.HealingPanelLayout:new (settings) local this = settings or {} setmetatable(this, self) self.__index = self return this end local groupNames = { [1] = "group01", [2] = "group02", [3] = "group03", [4] = "group04", [5] = "group05", [6] = "group06", [7] = "group07", [8] = "group08", [9] = "group09", [10] = "group10", [11] = "group11", [12] = "group12", [13] = "group13", [14] = "group14", [15] = "group15", [16] = "group16", [17] = "group17", [18] = "group18", [19] = "group19", [20] = "group20", } MereHealingFrames.HealingPanelSettings = { ManaHeight = 14, EnergyHeight = 14, ShowMana = true, ShowEnergy = true, ManaTextFormatter = "full", EnergyTextFormatter = "none", HealthTextFormatter = "full", ManaTextRounding = "thousandspoint", EnergyTextRounding = "none", HealthTextRounding = "thousandspoint", HealthTextBordered = true, ManaTextBordered = true, EnergyTextBordered = true, callingColouredText = true, borderPlayerName = true, callingColouredHealth = false, roleColouredHealth = false, borderSize = 2, buffConfiguration = nil, absorbShown = true, NameFontSize = 14, EnergyFontSize = nil, ManaFontSize = nil, HealthFontSize = 10, } function MereHealingFrames.HealingPanelSettings:new(panelSettings) local this = panelSettings or {} setmetatable(this, self) self.__index = self this.buffConfiguration = MereHealingFrames.HealingPanelBuffSettings:new(this.buffConfiguration) -- Convert the Heights into fontsizes for backwards compat. if (not this.EnergyFontSize) then this.EnergyFontSize = math.floor(MereHealingFrames.Config.UI.Utilities.CalcPointFromPixels(this.EnergyHeight)) end if (not this.ManaFontSize) then this.ManaFontSize = math.floor(MereHealingFrames.Config.UI.Utilities.CalcPointFromPixels(this.ManaHeight)) end return this end MereHealingFrames.HealingPanelBuffSettings = { topRow = 5, topRightToLeft = true, middleRow = 0, middleRightToLeft = false, bottomRow = 0, bottomRightToLeft = false, } function MereHealingFrames.HealingPanelBuffSettings:new(settings) local this = settings or {} setmetatable(this, self) self.__index = self this.slots = MereHealingFrames.HealingPanelBuffSlots:new(this.slots) return this end MereHealingFrames.HealingPanelBuffSlots = { 1,2,3,4,5, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, } function MereHealingFrames.HealingPanelBuffSlots:new(slots) local this = slots or {} setmetatable(this, self) self.__index = self return this end function MereHealingFrames.HealingPanelLayout.Create (settings) local layout = MereHealingFrames.HealingPanelLayout:new (settings) layout.Panels = {} layout.specifierMapping = {} layout.RoleFilter = layout.RoleFilter or { heal = true, tank = true, support = true, dps = true} layout.CallingFilter = layout.CallingFilter or { mage = true, cleric = true, warrior = true, rogue = true } layout.GroupingFilter = layout.GroupingFilter or { [1] = true, [2] = true, [3] = true, [4] = true } layout.PanelSettings = MereHealingFrames.HealingPanelSettings:new(layout.PanelSettings) layout:Initialize() return layout end function MereHealingFrames.HealingPanelLayout:Delete() self.Frame:SetVisible(false) for key, panel in pairs (self.Panels) do panel:GroupLeave() panel:SetVisible(false) MereHealingFrames.HealingPanelFactory.OldPanel(panel) end self.Panels = nil end function MereHealingFrames.HealingPanelLayout:RefreshPanelSettings() for key, panel in pairs (self.Panels) do panel:RefreshPanelSettings() end end function MereHealingFrames.HealingPanelLayout:Initialize (settings) self.Frame = UI.CreateFrame("Frame", "LayoutBackFrame", MereHealingFrames.context) self.Frame:SetSecureMode("restricted") MereHealingFrames.Debug(1, "Loading panel %s, AnchorPoint %s, position %d, %d", self.Name, self.AnchorPoint, self.TopLeftX, self.TopLeftY) self.Frame:SetPoint(self.AnchorPoint, UIParent, "TOPLEFT", self.TopLeftX, self.TopLeftY) self.Frame:SetBackgroundColor(MereHealingFrames.Colours.FetchRGBA("LayoutBackground")) self.Frame:SetLayer(MereHealingFrames.Layers.CalculateLayer(self.LayoutOffset, "LayoutBase")) self.NameFrame = UI.CreateFrame("Frame", "LayoutNameFrame", self.Frame ) self.NameFrame:SetHeight(self.LabelHeight) self.NameFrame:SetPoint("BOTTOMLEFT", self.Frame, "TOPLEFT") self.NameFrame:SetPoint("BOTTOMRIGHT", self.Frame, "TOPRIGHT") self.NameFrame:SetBackgroundColor(MereHealingFrames.Colours.FetchRGBA("LayoutBackground")) self.NameLabel = UI.CreateFrame("Text", "LayoutNameLabel", self.NameFrame) self.NameLabel:SetPoint("CENTER", self.NameFrame, "CENTER") self.NameLabel:SetText(self.Name) self.NameLabel:SetFontSize(self.LabelHeight - 2) self.NameLabel:SetWidth(self.NameFrame:GetWidth()) self:SetOrderBy(self.orderBy) self:Resize(self.Width, self.Height) self:UnlockLayout() end function MereHealingFrames.HealingPanelLayout:Resize(width, height) self.Width = width self.Height = height self:RelayPanels() end function MereHealingFrames.HealingPanelLayout:SetName(newName) self.Name = newName self.NameLabel:SetText(self.Name) end function MereHealingFrames.HealingPanelLayout:SetConfiguring(configuring) self.configuringLayout = configuring self.NameFrame:SetVisible(self.ShowNameLabel or self.configuringLayout) if (self.configuringLayout) then self.Frame:SetBackgroundColor(MereHealingFrames.Colours.FetchRGBA("LayoutBackgroundConfiguring")) self.NameFrame:SetBackgroundColor(MereHealingFrames.Colours.FetchRGBA("LayoutBackgroundConfiguring")) else self.Frame:SetBackgroundColor(MereHealingFrames.Colours.FetchRGBA("LayoutBackground")) self.NameFrame:SetBackgroundColor(MereHealingFrames.Colours.FetchRGBA("LayoutLabelBackground")) end end function MereHealingFrames.HealingPanelLayout:SetOrderBy(newOrder) local newLayoutFunc = MereHealingFrames.HealingPanelLayout.GroupLayouts[newOrder] if newLayoutFunc == nil then return false end self.layoutSequenceList = newLayoutFunc self.orderBy = newOrder self:RelayPanels() return true end function MereHealingFrames.HealingPanelLayout:RelayPanels() if self.isSecure then return end self.NameFrame:SetHeight(self.LabelHeight) self.NameLabel:SetFontSize(self.LabelHeight - 2) self:ApplyFilters() MereHealingFrames.Debug(4, "relaying %d panels", #self.Panels) self.NameFrame:SetVisible(self.ShowNameLabel or self.configuringLayout) local panel = self.specifierMapping["player"] if panel then local panelWidth = self.PanelWidth local panelHeight = self.PanelHeight -- solo layout, place player in the top left MereHealingFrames.Debug(4, "solo layout") panel:Move(self.BorderX, self.BorderY) panel:Resize(panelWidth, panelHeight) self.Frame:SetWidth(panelWidth + (2*self.BorderX)) self.Frame:SetHeight(panelHeight + (2*self.BorderY)) else self:RelayLayoutPanels() end self.NameLabel:SetWidth(self.NameFrame:GetWidth()) end function MereHealingFrames.HealingPanelLayout:calculateCompactedGroupList() local fillSize if self.FillRowsThenColumns then fillSize = self.PanelColumns else fillSize = self.PanelRows end local groupList = {} local currentEntry = 1 local specMapping = self.specifierMapping for i = 1, 20 do local groupName = groupNames[i] local panel = specMapping[groupName]; if panel then groupList[currentEntry] = panel currentEntry = currentEntry + 1 end if ((i % fillSize) == 0) then while currentEntry <= i do groupList[currentEntry] = false currentEntry = currentEntry + 1 end end end return groupList end function MereHealingFrames.HealingPanelLayout:calculateGroupList() local groupList = {} local specMapping = self.specifierMapping for i = 1, 20 do local groupName = groupNames[i] local panel = specMapping[groupName]; groupList[i] = panel or false end return groupList end function MereHealingFrames.HealingPanelLayout:calculateNamedList() local nameSort = {} for k, panel in pairs(self.Panels) do table.insert(nameSort, panel) end if #nameSort == 0 then MereHealingFrames.Debug(4, "no panels to display") return {} end table.sort(nameSort, function (a,b) return a.playerInfo.name < b.playerInfo.name end) return nameSort end local roleSort = { tank = 1, heal = 2, support = 3, dps = 4, unknown = 5, } function MereHealingFrames.HealingPanelLayout:calculateRoleAndNameSortedList() local nameSort = {} for k, panel in pairs(self.Panels) do table.insert(nameSort, panel) end if #nameSort == 0 then MereHealingFrames.Debug(4, "no panels to display") return {} end table.sort(nameSort, function (a,b) local roleA, roleB = roleSort[a.playerInfo.role], roleSort[b.playerInfo.role] if roleA < roleB then return true elseif roleA == roleB then return a.playerInfo.name < b.playerInfo.name else return false end end) return nameSort end function MereHealingFrames.HealingPanelLayout:RowCounter() --if self.FillBottomToTop then -- return self.PanelRows, 1, -1 --else return 1, self.PanelRows, 1 --end end function MereHealingFrames.HealingPanelLayout:ColumnCounter() --if self.FillRightToLeft then -- return self.PanelColumns, 1, -1 --else return 1, self.PanelColumns, 1 --end end function MereHealingFrames.HealingPanelLayout:LayoutAnchorPoint() local upDown, leftRight if self.FillBottomToTop then upDown = "BOTTOM" else upDown = "TOP" end if self.FillRightToLeft then leftRight = "RIGHT" else leftRight = "LEFT" end return upDown..leftRight end function MereHealingFrames.HealingPanelLayout:PositionPanel(column, row, panel, anchorPoint) if panel then local panelWidth, panelHeight = self.PanelWidth, self.PanelHeight local borderWidth, borderHeight = self.BorderX, self.BorderY local origPanelX = ( (panelWidth + self.PanelGapX) * (column - 1)) + borderWidth local origPanelY = ( (panelHeight + self.PanelGapY) * (row - 1)) + borderHeight local panelX, panelY if self.FillRightToLeft then panelX = -origPanelX - panelWidth else panelX = origPanelX end if self.FillBottomToTop then panelY = -origPanelY - panelHeight else panelY = origPanelY end panel:Move(panelX, panelY, anchorPoint) panel:Resize(panelWidth, panelHeight) return origPanelX + panelWidth + borderWidth, origPanelY + panelHeight + borderHeight end return 0, 0 end function MereHealingFrames.HealingPanelLayout:RelayLayoutPanels() local i = 1 local neededWidth, neededHeight = 0, 0 local anchorPoint = self:LayoutAnchorPoint() local groupList = self:layoutSequenceList() local mincol, maxcol, inccol = self:ColumnCounter() local minrow, maxrow, incrow = self:RowCounter() if self.FillRowsThenColumns then for row = minrow, maxrow, incrow do for column = mincol, maxcol, inccol do if (i > 20) then break end local bottomRightX, bottomRightY = self:PositionPanel(column, row, groupList[i], anchorPoint) neededWidth, neededHeight = math.max(bottomRightX, neededWidth), math.max(bottomRightY, neededHeight) i = i + 1 end end else for column = mincol, maxcol, inccol do for row = minrow, maxrow, incrow do if (i > 20) then break end local bottomRightX, bottomRightY = self:PositionPanel(column, row, groupList[i], anchorPoint) neededWidth, neededHeight = math.max(bottomRightX, neededWidth), math.max(bottomRightY, neededHeight) i = i + 1 end end end -- update the background frame to the right size self.Frame:SetWidth(neededWidth) self.Frame:SetHeight(neededHeight) end MereHealingFrames.HealingPanelLayout.GroupLayouts = { group = MereHealingFrames.HealingPanelLayout.calculateGroupList, name = MereHealingFrames.HealingPanelLayout.calculateNamedList, compactgroup = MereHealingFrames.HealingPanelLayout.calculateCompactedGroupList, roleandname = MereHealingFrames.HealingPanelLayout.calculateRoleAndNameSortedList, } function MereHealingFrames.HealingPanelLayout:SetVisible(visible) self.Frame:SetVisible(visible) self:RelayPanels() for k, panel in pairs(self.Panels) do panel:SetVisible(visible) end end function MereHealingFrames.HealingPanelLayout:UpdateSpells() for k, panel in pairs(self.Panels) do panel:UpdateSpells() end end function MereHealingFrames.HealingPanelLayout:QueueLayoutChange() self.PendingLayoutChange = true end function MereHealingFrames.HealingPanelLayout:ProcessPendingLayoutChanges() if self.isSecure then return end if self.PendingLayoutChange then self:SetVisible(self.Frame:GetVisible()) self.PendingLayoutChange = false end end function MereHealingFrames.HealingPanelLayout:GroupJoin(playerInfo, silent) silent = silent or false if self.isSecure then table.insert(MereHealingFrames.PostSecureCallbacks, function () self:GroupJoin(playerInfo) end) return end local unitId = playerInfo.unitId local groupSpecifier = playerInfo.specifier MereHealingFrames.Debug(4, "group join, UnitId: %s, specifier: %s, name: %s", unitId, playerInfo.specifier, playerInfo.name) if self.Panels[unitId] then MereHealingFrames.Debug(1, "Recursive join blocked for unitId: %s", unitId) return end local panelSettings = { } local panel = MereHealingFrames.HealingPanelFactory.NewPanel(self) self.Panels[unitId] = panel self.specifierMapping[groupSpecifier] = panel panel:Initialize(panelSettings, self, self.LayoutOffset) panel:SetPlayerInfo(playerInfo) MereHealingFrames.Debug(4, "making layout visible, panel for unitId: %s", unitId) if not silent then self:QueueLayoutChange() end end function MereHealingFrames.HealingPanelLayout:GroupLeave(unitId, groupSpecifier, silent) MereHealingFrames.Debug(3, "GroupLeave, unitId %s, specifier %s", (unitId or "noID?"), groupSpecifier or "no specifier?") local panel = self.Panels[unitId] if not panel then return end if self.isSecure then table.insert(MereHealingFrames.PostSecureCallbacks, function () self:GroupLeave(unitId, groupSpecifier) end) if (unitId ~= MereHealingFrames.PlayerId) then panel:GroupLeave() end return end self.Panels[unitId] = nil if not groupSpecifier then -- hunt for this entry for k, v in pairs(self.specifierMapping) do if v == panel then groupSpecifier = k break end end MereHealingFrames.Debug(3, "GroupLeave, unitId %s, found specifier %s", (unitId or "noID?"), groupSpecifier or "not found") end if (groupSpecifier) then self.specifierMapping[groupSpecifier] = nil end panel:SetVisible(false) MereHealingFrames.HealingPanelFactory.OldPanel(panel) silent = silent or false if not silent then self:QueueLayoutChange() end end function MereHealingFrames.HealingPanelLayout:GroupChange(unitId, originalSpecifier, newSpecifier) local panel = self.Panels[unitId] if not panel then return end if self.isSecure then table.insert(MereHealingFrames.PostSecureCallbacks, function () self:GroupChange(unitId, originalSpecifier, newSpecifier) end) return end local overwrittenPanel = self.specifierMapping[newSpecifier] if (overwrittenPanel) then -- the new location is in use, so we're just a little out of sequence, libsrm should remove the left unit MereHealingFrames.Debug(5, "GroupChange, will be overwritting another group member: %s", overwrittenPanel.playerInfo.name) end local panel = self.Panels[unitId] MereHealingFrames.Debug(5, "GroupChange, moving %s from %s to %s", panel.playerInfo.name, originalSpecifier, newSpecifier) -- only remove from the old specifier if that's actually where the player was if self.specifierMapping[originalSpecifier] == panel then self.specifierMapping[originalSpecifier] = nil end -- move the panel to the new specifier self.specifierMapping[newSpecifier] = panel -- trigger a relay self:QueueLayoutChange() end function MereHealingFrames.HealingPanelLayout:SecureEnter() self:LockLayout() self.isSecure = true end function MereHealingFrames.HealingPanelLayout:SecureLeave() self.isSecure = false self:UnlockLayout() end local ValidFilters = { heal = true, dps = true, tank = true, support = true } function MereHealingFrames.HealingPanelLayout:SetRoleFilter(filter) local newfilter = {} for i, value in ipairs(filter) do if not ValidFilters[value] then return false end newfilter[value] = true end self.RoleFilter = newfilter self:RelayPanels() return true end local ValidCallingFilters = { mage = true, cleric = true, warrior = true, rogue = true } local ValidGroupingFilters = { [1] = true, [2] = true, [3] = true, [4] = true } function MereHealingFrames.HealingPanelLayout:SetCallingFilter(filter) for key, value in pairs(filter) do if not ValidCallingFilters[key] then return false end end self.CallingFilter = filter self:RelayPanels() return true end function MereHealingFrames.HealingPanelLayout:SetGroupingFilter(filter) for key, value in pairs(filter) do if not ValidGroupingFilters[key] then return false end end self.GroupingFilter = filter self:RelayPanels() return true end function MereHealingFrames.HealingPanelLayout:ApplyFilters() local raidManagement = MereHealingFrames.RaidManagement local filterList = raidManagement.CreateUnitFilterList() -- accept unknown initially if self.RoleFilter then self.RoleFilter["unknown"] = true end if self.CallingFilter then self.CallingFilter["unknown"] = true end local roleFiler, callingFilter, groupingFilter = self.RoleFilter, self.CallingFilter, self.GroupingFilter local filterFunc = function(playerInfo) return roleFiler[playerInfo.role] and callingFilter[playerInfo.calling] and groupingFilter[playerInfo.grouping] end filterList = raidManagement.FilterUnitListByFunc(filterList, filterFunc) if self.RoleFilter then self.RoleFilter["unknown"] = nil end if self.CallingFilter then self.CallingFilter["unknown"] = nil end local units = raidManagement.Units local panels = self.Panels for unitId, displayed in pairs(filterList) do local panel = panels[unitId] if displayed and panel ~= nil then MereHealingFrames.Debug(4, "unitId %s is already displayed", unitId) elseif displayed then -- push a new panel creation through local playerInfo = units[unitId] self:GroupJoin(playerInfo, true) else -- delete a panel self:GroupLeave(unitId, nil, true) end end end function MereHealingFrames.HealingPanelLayout:UnlockLayout() if (MereHealingFrames.HealingPanelsLocked) then self:LockLayout() return end self.DragInfo = { dragging = false } self.Frame.Event.LeftDown = function (...) MereHealingFrames.HealingPanelLayout.LeftDown(self, ...) end self.Frame.Event.MouseMove = function (...) MereHealingFrames.HealingPanelLayout.MouseMove(self, ...) end self.Frame.Event.LeftUp = function (...) MereHealingFrames.HealingPanelLayout.LeftUp(self, ...) end self.Frame:SetMouseMasking("limited") self.Frame:SetBackgroundColor(MereHealingFrames.Colours.FetchRGBA("LayoutBackground")) self:LinkLabelFrameEvents() end function MereHealingFrames.HealingPanelLayout:LockLayout() self.DragInfo = nil self.Frame.Event.LeftDown = nil self.Frame.Event.MouseMove = nil self.Frame.Event.LeftUp = nil self.Frame:SetMouseMasking("limited") self:LinkLabelFrameEvents() self.Frame:SetBackgroundColor(MereHealingFrames.Colours.FetchRGBA("LayoutBackground")) end function MereHealingFrames.HealingPanelLayout:LinkLabelFrameEvents() self.NameFrame.Event.LeftDown = self.Frame.Event.LeftDown self.NameFrame.Event.MouseMove = self.Frame.Event.MouseMove self.NameFrame.Event.LeftUp = self.Frame.Event.LeftUp end function MereHealingFrames.HealingPanelLayout:LeftDown(...) if self.DragInfo then self.DragInfo.dragging = true self.DragInfo.offsetX = Inspect.Mouse().x - self.TopLeftX self.DragInfo.offsetY = Inspect.Mouse().y - self.TopLeftY self.DragInfo.LastX = self.TopLeftX self.DragInfo.LastY = self.TopLeftY end end function MereHealingFrames.HealingPanelLayout:MouseMove(event, x, y) if (self.DragInfo and self.DragInfo.dragging) then local newX = x - self.DragInfo.offsetX local newY = y - self.DragInfo.offsetY self.DragInfo.LastX = newX self.DragInfo.LastY = newY self.Frame:SetPoint(self.AnchorPoint, UIParent, "TOPLEFT", newX, newY) end end function MereHealingFrames.HealingPanelLayout:LeftUp(...) if self.DragInfo then self.DragInfo.dragging = false self.TopLeftX = self.DragInfo.LastX self.TopLeftY = self.DragInfo.LastY end end -- anchor is two part -- horizontal is TOP, CENTER, BOTTOM -- vertical is LEFT, CENTER, RIGHT -- Note I did wonder about taking the standard names, but realised that they'd all be internal till here function MereHealingFrames.HealingPanelLayout:ChangeAnchorPoint(horizontalAnchor, verticalAnchor) local left, top, right, bottom = self.Frame:GetBounds() local newX, newY if (horizontalAnchor == "TOP") then newY = top elseif (horizontalAnchor == "CENTER") then newY = top + ((bottom - top) / 2) elseif (horizontalAnchor == "BOTTOM") then newY = bottom else -- invalid param! return end if (verticalAnchor == "LEFT") then newX = left elseif(verticalAnchor == "CENTER") then newX = left + ((right-left) / 2) elseif(verticalAnchor == "RIGHT") then newX = right else return end local newAnchorPoint = horizontalAnchor .. verticalAnchor if (newAnchorPoint == "CENTERCENTER" ) then newAnchorPoint = "CENTER" end MereHealingFrames.Debug(2, "Layout Anchor point changed from %s to %s, from %d,%d to %d,%d", self.AnchorPoint, newAnchorPoint, self.TopLeftX, self.TopLeftY, newX, newY) self.Frame:ClearPoint(self.AnchorPoint) self.TopLeftX = newX self.TopLeftY = newY self.AnchorPoint = newAnchorPoint self.Frame:SetPoint(self.AnchorPoint, UIParent, "TOPLEFT", self.TopLeftX, self.TopLeftY) end