local Grid2 = Grid2 local Grid2Layout = Grid2:NewModule("Grid2Layout") local pairs, ipairs, next = pairs, ipairs, next --{{{ Frame config function for secure headers local function GridHeader_InitialConfigFunction(self, name) Grid2Frame:RegisterFrame(self) end --}}} --{{{ Class for group headers local NUM_HEADERS = 0 local SecureHeaderTemplates = { party = "SecurePartyHeaderTemplate", partypet = "SecurePartyPetHeaderTemplate", raid = "SecureRaidGroupHeaderTemplate", raidpet = "SecureRaidPetHeaderTemplate" } local GridLayoutHeaderClass = { prototype = {}, new = function(self, type) NUM_HEADERS = NUM_HEADERS + 1 local frame if (type == "spacer") then frame = CreateFrame("Frame", "Grid2LayoutHeader" .. NUM_HEADERS, Grid2Layout.frame) else frame = CreateFrame("Frame", "Grid2LayoutHeader" .. NUM_HEADERS, Grid2Layout.frame, assert(SecureHeaderTemplates[type])) frame:SetAttribute("template", _G.ClickCastHeader and "ClickCastUnitTemplate,SecureUnitButtonTemplate" or "Grid2SecureUnitButtonTemplate") frame.initialConfigFunction = GridHeader_InitialConfigFunction frame:SetAttribute("initialConfigFunction", [[ RegisterUnitWatch(self) self:SetAttribute("*type1", "target") self:SetAttribute("useparent-toggleForVehicle", true) self:SetAttribute("useparent-allowVehicleTarget", true) self:SetAttribute("useparent-unitsuffix", true) local header = self:GetParent() header:CallMethod("initialConfigFunction", self:GetName()) ]]) end for name, func in pairs(self.prototype) do frame[name] = func end frame:Reset() frame:SetOrientation() return frame end } local HeaderAttributes = { "showPlayer", "showSolo", "nameList", "groupFilter", "strictFiltering", "sortDir", "groupBy", "groupingOrder", "maxColumns", "unitsPerColumn", "startingIndex", "columnSpacing", "columnAnchorPoint", "useOwnerUnit", "filterOnPet", "unitsuffix", "allowVehicleTarget", "toggleForVehicle" } function GridLayoutHeaderClass.prototype:Reset() if self.initialConfigFunction then self:SetLayoutAttribute("sortMethod", "NAME") for _, attr in ipairs(HeaderAttributes) do self:SetLayoutAttribute(attr, nil) end end self:Hide() end local anchorPoints = { [false] = {TOPLEFT = "TOP", TOPRIGHT = "TOP", BOTTOMLEFT = "BOTTOM", BOTTOMRIGHT = "BOTTOM"}, [true] = {TOPLEFT = "LEFT", TOPRIGHT = "RIGHT", BOTTOMLEFT = "LEFT", BOTTOMRIGHT = "RIGHT"}, TOP = -1, BOTTOM = 1, LEFT = 1, RIGHT = -1 } -- nil or false for vertical function GridLayoutHeaderClass.prototype:SetOrientation(horizontal) if not self.initialConfigFunction then return end local settings = Grid2Layout.db.profile local vertical = not horizontal local point = anchorPoints[not vertical][settings.groupAnchor] local direction = anchorPoints[point] local xOffset = horizontal and settings.Padding * direction or 0 local yOffset = vertical and settings.Padding * direction or 0 self:SetLayoutAttribute("xOffset", xOffset) self:SetLayoutAttribute("yOffset", yOffset) self:SetLayoutAttribute("point", point) end -- MSaint fix see: http://forums.wowace.com/showpost.php?p=315982&postcount=215 -- To maintain the code consistent all calls to SetAttribute were replaced with SetLayoutAttribute -- including those which not affect anchors, the only exception: calls from GridLayoutHeaderClass.new) function GridLayoutHeaderClass.prototype:SetLayoutAttribute(name, value) if name == "point" or name == "columnAnchorPoint" or name == "unitsPerColumn" then self:ClearChildrenPoints() end self:SetAttribute(name, value) end function GridLayoutHeaderClass.prototype:ClearChildrenPoints() local count = 1 local uframe = self:GetAttribute("child1") while uframe do uframe:ClearAllPoints() count = count + 1 uframe = self:GetAttribute("child" .. count) end end --{{{ Grid2Layout -- AceDB defaults Grid2Layout.defaultDB = { profile = { debug = false, FrameDisplay = "Always", layouts = { solo = "Solo w/Pet", party = "By Group 5 w/Pets", raid10 = "By Group 10 w/Pets", raid15 = "By Group 15 w/Pets", raid25 = "By Group 25 w/Pets", raid40 = "By Group 40", arena = "By Group 5 w/Pets" }, layoutScales = {}, horizontal = true, clamp = true, FrameLock = false, ClickThrough = false, Padding = 0, Spacing = 10, ScaleSize = 1, BorderTexture = "Blizzard Tooltip", BorderR = .5, BorderG = .5, BorderB = .5, BorderA = 1, BackgroundR = .1, BackgroundG = .1, BackgroundB = .1, BackgroundA = .65, anchor = "TOPLEFT", groupAnchor = "TOPLEFT", PosX = 500, PosY = -200 } } Grid2Layout.frameBackdrop = { bgFile = "Interface\\ChatFrame\\ChatFrameBackground", tile = true, tileSize = 16, edgeSize = 16, insets = {left = 4, right = 4, top = 4, bottom = 4} } Grid2Layout.layoutSettings = {} Grid2Layout.layoutHeaderClass = GridLayoutHeaderClass function Grid2Layout:OnModuleInitialize() self.groups = { raid = {}, raidpet = {}, party = {}, partypet = {}, spacer = {} } self.indexes = { raid = 0, raidpet = 0, party = 0, partypet = 0, spacer = 0 } self:AddCustomLayouts() end function Grid2Layout:OnModuleEnable() if not self.frame then self:CreateFrame() end self:RestorePosition() if self.layoutName then self:ReloadLayout() end self:RegisterMessage("Grid_GroupTypeChanged") self:RegisterMessage("Grid_UpdateLayoutSize", "UpdateSize") self:RegisterEvent("PLAYER_REGEN_ENABLED") end function Grid2Layout:OnModuleDisable() self:UnregisterMessage("Grid_GroupTypeChanged") self:UnregisterMessage("Grid_UpdateLayoutSize", "UpdateSize") self:UnregisterEvent("PLAYER_REGEN_ENABLED") self.frame:Hide() end --{{{ Event handlers function Grid2Layout:PLAYER_REGEN_ENABLED() if self.reloadLayoutQueued then return self:ReloadLayout() end if self.updateSizeQueued then return self:UpdateSize() end if self.restorePositionQueued then return self:RestorePosition() end end function Grid2Layout:Grid_GroupTypeChanged(_, type) Grid2Layout:Debug("GroupTypeChanged", type) self.partyType = type self:ReloadLayout() end --}}} function Grid2Layout:StartMoveFrame(button) if not self.db.profile.FrameLock and button == "LeftButton" then self.frame:StartMoving() self.frame.isMoving = true end end function Grid2Layout:StopMoveFrame() if self.frame.isMoving then self.frame:StopMovingOrSizing() self:SavePosition() self.frame.isMoving = false self:RestorePosition() end end -- nil:toggle, false:disable movement, true:enable movement function Grid2Layout:FrameLock(locked) local p = self.db.profile if (locked == nil) then p.FrameLock = not p.FrameLock else p.FrameLock = locked end if (not p.FrameLock and p.ClickThrough) then p.ClickThrough = false self.frame:EnableMouse(true) end end --{{{ ConfigMode support CONFIGMODE_CALLBACKS = CONFIGMODE_CALLBACKS or {} CONFIGMODE_CALLBACKS["Grid2"] = function(action) if (action == "ON") then Grid2Layout:FrameLock(false) elseif (action == "OFF") then Grid2Layout:FrameLock(true) end end --}}} function Grid2Layout:CreateFrame() local p = self.db.profile -- create main frame to hold all our gui elements local f = CreateFrame("Frame", "Grid2LayoutFrame", UIParent) self.frame = f f:SetMovable(true) f:SetClampedToScreen(p.clamp) f:SetPoint("CENTER", UIParent, "CENTER") f:SetScript("OnMouseUp", function() self:StopMoveFrame() end) f:SetScript("OnHide", function() self:StopMoveFrame() end) f:SetScript("OnMouseDown", function(_, button) self:StartMoveFrame(button) end) f:SetFrameStrata(p.FrameStrata or "MEDIUM") f:SetFrameLevel(0) self:UpdateTextures() self:SetFrameLock(p.FrameLock, p.ClickThrough) self.CreateFrame = nil end local relativePoints = { [false] = {TOPLEFT = "BOTTOMLEFT", TOPRIGHT = "BOTTOMRIGHT", BOTTOMLEFT = "TOPLEFT", BOTTOMRIGHT = "TOPRIGHT"}, [true] = {TOPLEFT = "TOPRIGHT", TOPRIGHT = "TOPLEFT", BOTTOMLEFT = "BOTTOMRIGHT", BOTTOMRIGHT = "BOTTOMLEFT"}, xMult = {TOPLEFT = 1, TOPRIGHT = -1, BOTTOMLEFT = 1, BOTTOMRIGHT = -1}, yMult = {TOPLEFT = -1, TOPRIGHT = -1, BOTTOMLEFT = 1, BOTTOMRIGHT = 1} } local previousFrame function Grid2Layout:PlaceGroup(frame, groupNumber) local settings = self.db.profile local horizontal = settings.horizontal local vertical = not horizontal local padding = settings.Padding local spacing = settings.Spacing local anchor = settings.groupAnchor local relPoint = relativePoints[vertical][anchor] local xMult = relativePoints.xMult[anchor] local yMult = relativePoints.yMult[anchor] frame:ClearAllPoints() frame:SetParent(self.frame) if groupNumber == 1 then frame:SetPoint(anchor, self.frame, anchor, spacing * xMult, spacing * yMult) else xMult = vertical and xMult * padding or 0 yMult = horizontal and yMult * padding or 0 frame:SetPoint(anchor, previousFrame, relPoint, xMult, yMult) end self:Debug("Placing group", groupNumber, frame:GetName(), anchor, previousFrame and previousFrame:GetName(), relPoint) previousFrame = frame end function Grid2Layout:AddLayout(layoutName, layout) self.layoutSettings[layoutName] = layout end function Grid2Layout:SetClamp() self.frame:SetClampedToScreen(self.db.profile.clamp) end function Grid2Layout:ReloadLayout(force) if InCombatLockdown() and not force then self.reloadLayoutQueued = true return end self.reloadLayoutQueued = nil self:LoadLayout(self.db.profile.layouts[self.partyType or "solo"]) end function Grid2Layout:RefreshLayout() return self:ReloadLayout(true) end local function SetAllAttributes(header, p, list, fix) local petgroup = false for attr, value in next, list do if attr == "unitsPerColumn" then header:SetLayoutAttribute("columnSpacing", p.Padding) header:SetLayoutAttribute("unitsPerColumn", value) header:SetLayoutAttribute("columnAnchorPoint", anchorPoints[not p.horizontal][p.groupAnchor] or p.groupAnchor) elseif attr ~= "type" then header:SetLayoutAttribute(attr, value) else petgroup = (value == "partypet" or value == "raidpet") end end if fix and petgroup then -- force these so that the bug in SecureGroupPetHeader_Update doesn't trigger header:SetLayoutAttribute("useOwnerUnit", false) header:SetLayoutAttribute("unitsuffix", nil) end end -- Precreate frames to avoid a blizzard bug that prevents initializing unit frames in combat -- http://forums.wowace.com/showpost.php?p=307503&postcount=3163 local function ForceFramesCreation(header) local startingIndex = header:GetAttribute("startingIndex") local maxColumns = header:GetAttribute("maxColumns") or 1 local unitsPerColumn = header:GetAttribute("unitsPerColumn") or 5 local maxFrames = maxColumns * unitsPerColumn local count = header.FrameCount if not count or count < maxFrames then header:Show() header:SetAttribute("startingIndex", 1 - maxFrames) header:SetAttribute("startingIndex", startingIndex) header.FrameCount = maxFrames end end function Grid2Layout:LoadLayout(layoutName) local layout = self.layoutSettings[layoutName] if not layout then return end self:Debug("LoadLayout", layoutName) self.layoutName = layoutName self:Scale() local p = self.db.profile local horizontal = p.horizontal for type, headers in pairs(self.groups) do self.indexes[type] = 0 for _, g in ipairs(headers) do g:Reset() end end local defaults = layout.defaults local default_type = defaults and defaults.type or "raid" for i, l in ipairs(layout) do local type = l.type or default_type local headers = assert(self.groups[type], "Bad " .. type) local index = self.indexes[type] + 1 local layoutGroup = headers[index] if not layoutGroup then layoutGroup = self.layoutHeaderClass:new(type) headers[index] = layoutGroup end self.indexes[type] = index if type ~= "spacer" then if defaults then SetAllAttributes(layoutGroup, p, defaults) end SetAllAttributes(layoutGroup, p, l, true) ForceFramesCreation(layoutGroup) layoutGroup:SetOrientation(horizontal) end self:PlaceGroup(layoutGroup, i) layoutGroup:Show() end self:UpdateDisplay() end function Grid2Layout:UpdateDisplay() self:UpdateTextures() self:UpdateColor() self:CheckVisibility() self:UpdateSize() end function Grid2Layout:UpdateSize() if InCombatLockdown() then self.updateSizeQueued = true return end self.updateSizeQueued = nil local p = self.db.profile local curWidth, curHeight, maxWidth, maxHeight = 0, 0, 0, 0 local Padding, Spacing = p.Padding, p.Spacing * 2 local frameWidth, frameHeight = Grid2Frame:GetFrameSize() for i = 1, self.indexes.spacer do self.groups.spacer[i]:SetSize(frameWidth, frameHeight) end for type, headers in pairs(self.groups) do for i = 1, self.indexes[type] do local g = headers[i] local width, height = g:GetWidth(), g:GetHeight() curWidth = curWidth + width + Padding curHeight = curHeight + height + Padding if maxWidth < width then maxWidth = width end if maxHeight < height then maxHeight = height end end end local x = p.horizontal and maxWidth + Spacing or curWidth + Spacing - Padding local y = p.horizontal and curHeight + Spacing - Padding or maxHeight + Spacing self.frame:SetWidth(x) self.frame:SetHeight(y) end function Grid2Layout:UpdateTextures() local f = self.frame local p = self.db.profile -- update backdrop data self.frameBackdrop.bgFile = Grid2:MediaFetch("background", p.BackgroundTexture, "Interface\\ChatFrame\\ChatFrameBackground") self.frameBackdrop.edgeFile = Grid2:MediaFetch("border", p.BorderTexture) self.frameBackdrop.tile = p.BackgroundTile self.frameBackdrop.tileSize = p.BackgroundTileSize or 16 f:SetBackdrop(self.frameBackdrop) end function Grid2Layout:UpdateColor() local settings = self.db.profile self.frame:SetBackdropBorderColor(settings.BorderR, settings.BorderG, settings.BorderB, settings.BorderA) self.frame:SetBackdropColor(settings.BackgroundR, settings.BackgroundG, settings.BackgroundB, settings.BackgroundA) end function Grid2Layout:CheckVisibility() local frameDisplay = self.db.profile.FrameDisplay if (frameDisplay == "Always") or (frameDisplay == "Grouped" and self.partyType ~= "solo") or (frameDisplay == "Raid" and self.partyType:find("raid")) then self.frame:Show() else self.frame:Hide() end end function Grid2Layout:SavePosition() local f = self.frame if f:GetLeft() and f:GetWidth() then local a = self.db.profile.anchor local s = f:GetEffectiveScale() local t = UIParent:GetEffectiveScale() local x = (a:find("LEFT") and f:GetLeft() * s) or (a:find("RIGHT") and f:GetRight() * s - UIParent:GetWidth() * t) or (f:GetLeft() + f:GetWidth() / 2) * s - UIParent:GetWidth() / 2 * t local y = (a:find("BOTTOM") and f:GetBottom() * s) or (a:find("TOP") and f:GetTop() * s - UIParent:GetHeight() * t) or (f:GetTop() - f:GetHeight() / 2) * s - UIParent:GetHeight() / 2 * t self.db.profile.PosX = x self.db.profile.PosY = y self:Debug("Saved Position", a, x, y) end end function Grid2Layout:ResetPosition() local s = UIParent:GetEffectiveScale() self.db.profile.PosX = UIParent:GetWidth() / 2 * s self.db.profile.PosY = -UIParent:GetHeight() / 2 * s self.db.profile.anchor = "TOPLEFT" self:RestorePosition() self:SavePosition() end function Grid2Layout:RestorePosition() if InCombatLockdown() then self.restorePositionQueued = true return end self.restorePositionQueued = nil local f = self.frame local s = f:GetEffectiveScale() local x = self.db.profile.PosX / s local y = self.db.profile.PosY / s local a = self.db.profile.anchor f:ClearAllPoints() f:SetPoint(a, x, y) self:Debug("Restored Position", a, x, y) end function Grid2Layout:Scale() local settings = self.db.profile self:SavePosition() self.frame:SetScale(settings.ScaleSize * (settings.layoutScales[self.layoutName or "solo"] or 1)) self:RestorePosition() end function Grid2Layout:SetFrameLock(FrameLock, ClickThrough) local p = self.db.profile p.FrameLock = FrameLock if not FrameLock then ClickThrough = false end p.ClickThrough = ClickThrough self.frame:EnableMouse(not ClickThrough) end function Grid2Layout:AddCustomLayouts() local customLayouts = self.db.global.customLayouts if customLayouts then for n, l in pairs(customLayouts) do Grid2Layout:AddLayout(n, l) end end end _G.Grid2Layout = Grid2Layout