Files
Adrestie ca2b78e217 Remove every debug command
/fui keeps edit mode, reset and list. The thirty diagnostic and tuning
functions behind the other subcommands (bar, bags, character, tabs, close,
model, minimap, mouse, map, questlog, chat, chatlines, tabard...) go with
them, with Panes.Report, the bags read-back and the 205 texts they alone used.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 13:39:35 +02:00

1150 lines
42 KiB
Lua

-- Social window skinned like camelot: the frame, its tabs and the Contacts page (friends
-- and ignores). The Who, Guild, Chat and Raid pages are in SocialWho.lua, SocialGuild.lua,
-- SocialChat.lua and SocialRaid.lua and register with S.registerPage. Data, row order,
-- colors, menu, tooltip and button actions follow the client's FriendsFrame.lua.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local S = {}
ForeverUI.Social = S
local SEP = string.char(92)
local L = ForeverUI.L
-- Layout values from camelot friendsframe.xml, shareduipaneltemplates, nineslicelayouts
-- and tabsystemtemplates. A table, because Lua 5.1 limits a function to 60 upvalues.
local G = {
width = 385, height = 424,
rock = "interface" .. SEP .. "ForeverUI" .. SEP .. "framegeneral" .. SEP .. "ui-background-rock",
-- battlenet-portrait refined by tools/refine_portrait.py: 128 px, mipmaps, uncompressed;
-- the original 64 px DXT5 looks pixelated
portrait = "Interface" .. SEP .. "ForeverUI" .. SEP .. "friendsframe" .. SEP .. "battlenet-portrait-hd",
portraitSide = 60, portraitX = -5, portraitY = 7,
titleX1 = 58, titleX2 = -24, titleY = -1, titleH = 20, titleTextY = -5,
closeButton = 24, closeButtonX = -2, closeButtonY = 1,
frameBoxX1 = 4, frameBoxY1 = -83, frameBoxX2 = -6, frameBoxY2 = 26,
listX = 8, listY = -87, listX2 = -22, listY2 = 2, listX2NoBar = -4,
buttonW = 134, buttonH = 21, buttonBottom = 4, buttonLeft = 4, buttonRight = -6,
tabH = 32, firstTabX = 5, firstTabY = 2, tabGap = 3, tabMargin = 20,
subTabX = 18, subTabY = -60, subTabH = 24, subTabMin = 100, subTabMax = 150, subTabGap = 1,
friendRowH = 34, shortRowH = 16,
stateSide = 16, stateX = 4, stateY = -3, nameX = 20, nameY = -4, infoY = -3,
highlight = "Interface" .. SEP .. "QuestFrame" .. SEP .. "UI-QuestLogTitleHighlight",
ignoreHighlight = "Interface" .. SEP .. "QuestFrame" .. SEP .. "UI-QuestTitleHighlight",
highlightTint = { 0.243, 0.570, 1 },
separator = "Interface" .. SEP .. "FriendsFrame" .. SEP .. "UI-FriendsFrame-OnlineDivider",
state = "Interface" .. SEP .. "FriendsFrame" .. SEP .. "StatusIcon-",
}
-- PortraitFrameTemplate metal with camelot's fixes (nineslicelayoutoverrides.lua): right
-- corners at x = 2, bottom corners at y = -8.
local METAL = {
{ key = "topLeft", name = "ui-frame-portraitmetal-cornertopleft", point = "TOPLEFT", x = -13, y = 16 },
{ key = "topRight", name = "ui-frame-metal-cornertopright", point = "TOPRIGHT", x = 2, y = 16 },
{ key = "bottomLeft", name = "ui-frame-metal-cornerbottomleft", point = "BOTTOMLEFT", x = -13, y = -8 },
{ key = "bottomRight", name = "ui-frame-metal-cornerbottomright", point = "BOTTOMRIGHT", x = 2, y = -8 },
}
-- Tab art: active pieces from the base sheet (camelot has no c60 ones), inactive from c60.
local TAB_ART = {
activeLeft = "uiframe-activetab-left", activeMiddle = "_uiframe-activetab-center", activeRight = "uiframe-activetab-right",
inactiveLeft = "uiframe-tab-left-c60", inactiveMiddle = "_uiframe-tab-center-c60", inactiveRight = "uiframe-tab-right-c60",
}
-- Bottom tabs: Contacts, then the WotLK ones. id is the client tab's, FriendsFrameTab<id>.
-- 3.3.5 lacks camelot's CONTACTS_* strings, so tabs and titles use FRIENDS, FRIENDS_LIST
-- and IGNORE_LIST.
local TABS = {
{ id = 1, text = "FRIENDS" },
{ id = 2, text = "WHO" },
{ id = 3, text = "GUILD" },
{ id = 4, text = "CHAT" },
{ id = 5, text = "RAID" },
}
local SUB_TABS = {
{ id = 1, text = "FRIENDS", title = "FRIENDS_LIST" },
{ id = 2, text = "IGNORE", title = "IGNORE_LIST" },
}
-- Client global string, or the key itself when missing.
local function txt(key)
return _G[key] or key
end
-- r, g, b, a of the client color global name, or of default when missing
local function color(name, default)
local c = _G[name]
if type(c) == "table" and c.r then
return c.r, c.g, c.b, c.a
end
return default[1], default[2], default[3], default[4]
end
-- ------------------------------------------------------------------ Frame
-- Builds the window art: rock background, top streaks, metal border, portrait, title
-- and close button.
local function buildFrame(f)
local rock = f:CreateTexture(nil, "BACKGROUND")
rock:SetTexture(G.rock, true)
if rock.SetHorizTile then rock:SetHorizTile(true) rock:SetVertTile(true) end
rock:SetPoint("TOPLEFT", f, "TOPLEFT", 2, -21)
rock:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -2, 2)
local stripes = f:CreateTexture(nil, "BORDER")
ForeverUI.SetAtlas(stripes, "_ui-frame-toptilestreaks", true)
stripes:SetHeight(43)
stripes:SetPoint("TOPLEFT", f, "TOPLEFT", 6, -21)
stripes:SetPoint("TOPRIGHT", f, "TOPRIGHT", -2, -21)
local metal = CreateFrame("Frame", nil, f)
metal:SetAllPoints(f)
metal:SetFrameLevel(f:GetFrameLevel() + 20)
local p = {}
for _, corner in ipairs(METAL) do
local t = metal:CreateTexture(nil, "OVERLAY")
ForeverUI.SetAtlas(t, corner.name)
t:SetPoint(corner.point, metal, corner.point, corner.x, corner.y)
p[corner.key] = t
end
local function edge(name, a1, c1, r1, a2, c2, r2)
local t = metal:CreateTexture(nil, "OVERLAY")
ForeverUI.SetAtlas(t, name)
t:SetPoint(a1, c1, r1)
t:SetPoint(a2, c2, r2)
end
edge("_ui-frame-metal-edgetop", "TOPLEFT", p.topLeft, "TOPRIGHT", "TOPRIGHT", p.topRight, "TOPLEFT")
edge("_ui-frame-metal-edgebottom", "BOTTOMLEFT", p.bottomLeft, "BOTTOMRIGHT", "BOTTOMRIGHT", p.bottomRight, "BOTTOMLEFT")
edge("!ui-frame-metal-edgeleft", "TOPLEFT", p.topLeft, "BOTTOMLEFT", "BOTTOMLEFT", p.bottomLeft, "TOPLEFT")
edge("!ui-frame-metal-edgeright", "TOPRIGHT", p.topRight, "BOTTOMRIGHT", "BOTTOMRIGHT", p.bottomRight, "TOPRIGHT")
local portraitFrame = CreateFrame("Frame", nil, f)
portraitFrame:SetAllPoints(f)
portraitFrame:SetFrameLevel(f:GetFrameLevel() + 19)
local portrait = portraitFrame:CreateTexture(nil, "OVERLAY")
portrait:SetWidth(G.portraitSide)
portrait:SetHeight(G.portraitSide)
portrait:SetPoint("TOPLEFT", f, "TOPLEFT", G.portraitX, G.portraitY)
portrait:SetTexture(G.portrait)
f.portrait = portrait
local banner = CreateFrame("Frame", nil, f)
banner:SetFrameLevel(f:GetFrameLevel() + 21)
banner:SetHeight(G.titleH)
banner:SetPoint("TOPLEFT", f, "TOPLEFT", G.titleX1, G.titleY)
banner:SetPoint("TOPRIGHT", f, "TOPRIGHT", G.titleX2, G.titleY)
f.title = banner:CreateFontString(nil, "OVERLAY", "GameFontNormal")
f.title:SetPoint("TOP", banner, "TOP", 0, G.titleTextY)
f.banner = banner
-- Close button: clicks the client's (SecureTemplates "click" action), whose HideParentPanel
-- then hides FriendsFrame as client code. Protected by the secure tabs, FriendsFrame could
-- not be hidden by ours in combat.
local closeButton = CreateFrame("Button", "ForeverUISocialCloseButton", f, "SecureActionButtonTemplate")
closeButton:SetWidth(G.closeButton)
closeButton:SetHeight(G.closeButton)
closeButton:SetFrameLevel(f:GetFrameLevel() + 22)
closeButton:SetPoint("TOPRIGHT", f, "TOPRIGHT", G.closeButtonX, G.closeButtonY)
for _, state in ipairs({
{ "SetNormalTexture", "GetNormalTexture", "redbutton-exit" },
{ "SetPushedTexture", "GetPushedTexture", "redbutton-exit-pressed" },
{ "SetHighlightTexture", "GetHighlightTexture", "redbutton-highlight" },
}) do
local e = ForeverUI.AtlasEntry(state[3])
closeButton[state[1]](closeButton, e and e[1] or "")
local t = closeButton[state[2]](closeButton)
if t then
ForeverUI.SetAtlas(t, state[3], true)
t:ClearAllPoints()
t:SetAllPoints(closeButton)
if state[3] == "redbutton-highlight" then t:SetBlendMode("ADD") end
end
end
closeButton:SetAttribute("type", "click")
closeButton:SetAttribute("clickbutton", FriendsFrameCloseButton)
f.closeButton = closeButton
end
-- --------------------------------------------------------------- Tabs
-- One tab texture. Top tabs use it flipped: a half turn swaps both edges in width and
-- height (SetRotation would drop the atlas rectangle).
local function tabPiece(b, layer, atlas, flipped)
local t = b:CreateTexture(nil, layer)
ForeverUI.SetAtlas(t, atlas)
if flipped then
local e = ForeverUI.AtlasEntry(atlas)
if e then t:SetTexCoord(e[3], e[2], e[5], e[4]) end
end
return t
end
-- PanelTabButtonTemplate (bottom) or TabSystemButtonArtTemplate + isTabOnTop (top).
-- Two sets of three pieces, active and inactive, plus the highlight: inactive art in ADD
-- at 0.4. atTop: sub-tab above the list; template: optional frame template
local function createTab(parent, name, atTop, template)
local b = CreateFrame("Button", name, parent, template)
b:SetHeight(atTop and G.subTabH or G.tabH)
local a = {}
a.activeLeft = tabPiece(b, "BACKGROUND", TAB_ART.activeLeft, atTop)
a.activeRight = tabPiece(b, "BACKGROUND", TAB_ART.activeRight, atTop)
a.activeMiddle = tabPiece(b, "BACKGROUND", TAB_ART.activeMiddle, atTop)
a.g = tabPiece(b, "BACKGROUND", TAB_ART.inactiveLeft, atTop)
a.d = tabPiece(b, "BACKGROUND", TAB_ART.inactiveRight, atTop)
a.m = tabPiece(b, "BACKGROUND", TAB_ART.inactiveMiddle, atTop)
a.sg = tabPiece(b, "HIGHLIGHT", TAB_ART.inactiveLeft, atTop)
a.sd = tabPiece(b, "HIGHLIGHT", TAB_ART.inactiveRight, atTop)
a.sm = tabPiece(b, "HIGHLIGHT", TAB_ART.inactiveMiddle, atTop)
for _, t in ipairs({ a.sg, a.sd, a.sm }) do
t:SetBlendMode("ADD")
t:SetAlpha(0.4)
end
if atTop then
-- HandleRotation: the flipped right piece moves to the left; SetTabHeight(24) sets all
-- heights
for _, t in pairs(a) do t:SetHeight(G.subTabH) end
a.activeRight:SetPoint("BOTTOMLEFT", b, "BOTTOMLEFT", -7, 0)
a.activeLeft:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 0, 0)
a.d:SetPoint("BOTTOMLEFT", b, "BOTTOMLEFT", -6, 0)
a.g:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 0, 0)
a.activeMiddle:SetPoint("TOPLEFT", a.activeRight, "TOPRIGHT", 0, 0)
a.activeMiddle:SetPoint("BOTTOMRIGHT", a.activeLeft, "BOTTOMLEFT", 0, 0)
a.m:SetPoint("TOPLEFT", a.d, "TOPRIGHT", 0, 0)
a.m:SetPoint("BOTTOMRIGHT", a.g, "BOTTOMLEFT", 0, 0)
a.sg:SetPoint("TOPRIGHT", a.g, "TOPRIGHT", 0, 0)
a.sd:SetPoint("TOPLEFT", a.d, "TOPLEFT", 0, 0)
a.sm:SetPoint("TOPLEFT", a.m, "TOPLEFT", 0, 0)
a.sm:SetPoint("BOTTOMRIGHT", a.m, "BOTTOMRIGHT", 0, 0)
else
a.activeLeft:SetPoint("TOPLEFT", b, "TOPLEFT", -1, 0)
a.activeRight:SetPoint("TOPRIGHT", b, "TOPRIGHT", 8, 0)
a.g:SetPoint("TOPLEFT", b, "TOPLEFT", -3, 0)
a.d:SetPoint("TOPRIGHT", b, "TOPRIGHT", 7, 0)
a.activeMiddle:SetPoint("TOPLEFT", a.activeLeft, "TOPRIGHT", 0, 0)
a.activeMiddle:SetPoint("BOTTOMRIGHT", a.activeRight, "BOTTOMLEFT", 0, 0)
a.m:SetPoint("TOPLEFT", a.g, "TOPRIGHT", 0, 0)
a.m:SetPoint("BOTTOMRIGHT", a.d, "BOTTOMLEFT", 0, 0)
a.sg:SetPoint("TOPLEFT", a.g, "TOPLEFT", 0, 0)
a.sd:SetPoint("TOPRIGHT", a.d, "TOPRIGHT", 0, 0)
a.sm:SetPoint("TOPLEFT", a.m, "TOPLEFT", 0, 0)
a.sm:SetPoint("BOTTOMRIGHT", a.m, "BOTTOMRIGHT", 0, 0)
end
b.art = a
b.atTop = atTop
local fs = b:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
fs:SetHeight(10)
b:SetFontString(fs)
b.text = fs
return b
end
-- SetTabSelected / PanelTemplates_SelectTab: active art, highlight font, text lowered
-- (bottom) or raised (top), selected tab disabled.
-- selected: this tab is chosen; active: the tab can be clicked
local function selectTab(b, selected, active)
local a = b.art
for _, t in ipairs({ a.activeLeft, a.activeMiddle, a.activeRight }) do
if selected then t:Show() else t:Hide() end
end
for _, t in ipairs({ a.g, a.m, a.d }) do
if selected then t:Hide() else t:Show() end
end
local font = selected and GameFontHighlightSmall or (active and GameFontNormalSmall or GameFontDisableSmall)
b.text:SetFontObject(font)
b:SetNormalFontObject(font)
local y
if b.atTop then
y = selected and 0 or -3
else
y = selected and -3 or 2
end
b.text:ClearAllPoints()
b.text:SetPoint("CENTER", b, "CENTER", 0, y)
if b.secureTab then
-- A secure tab cannot be disabled in combat, and need not be: it clicks the client's
-- tab, which the client disables when it is chosen or unavailable. Only the highlight
-- goes, as on a disabled tab.
for _, t in ipairs({ a.sg, a.sd, a.sm }) do
if selected or not active then t:Hide() else t:Show() end
end
elseif selected or not active then
b:Disable()
else
b:Enable()
end
end
-- PanelTemplates_TabResize / TabSystemButtonMixin:UpdateTabWidth
local function tabWidth(b)
local text = b.text:GetStringWidth() or 0
local g = ForeverUI.AtlasEntry(TAB_ART.inactiveLeft)
local d = ForeverUI.AtlasEntry(TAB_ART.inactiveRight)
local sides = (g and g[6] or 35) + (d and d[6] or 37)
if b.atTop then
local l = sides + 20
if l < text then l = text + 10 end
return math.max(G.subTabMin, math.min(G.subTabMax, l))
end
return math.max(sides, text + G.tabMargin)
end
-- bottom tabs, shared with other windows (raid browser)
S.createTab, S.selectTab, S.tabWidth = createTab, selectTab, tabWidth
-- ------------------------------------------------------------------ List
local ROWS = {} -- rows created once, then reused
-- Creates list row n, holding the parts of every row kind.
local function createRow(n)
local l = CreateFrame("Button", "ForeverUISocialRow" .. n, S.list)
l:RegisterForClicks("LeftButtonUp", "RightButtonUp")
l.background = l:CreateTexture(nil, "BACKGROUND")
l.background:SetPoint("TOPLEFT", l, "TOPLEFT", 0, -1)
l.background:SetPoint("BOTTOMRIGHT", l, "BOTTOMRIGHT", 0, 1)
l.state = l:CreateTexture(nil, "ARTWORK")
l.state:SetWidth(G.stateSide)
l.state:SetHeight(G.stateSide)
l.state:SetPoint("TOPLEFT", l, "TOPLEFT", G.stateX, G.stateY)
l.name = l:CreateFontString(nil, "ARTWORK", "FriendsFont_Normal")
l.name:SetJustifyH("LEFT")
l.name:SetHeight(12)
l.info = l:CreateFontString(nil, "ARTWORK", "FriendsFont_Small")
l.info:SetJustifyH("LEFT")
l.info:SetHeight(10)
l.info:SetPoint("TOPLEFT", l.name, "BOTTOMLEFT", 0, G.infoY)
l.info:SetPoint("TOPRIGHT", l.name, "BOTTOMRIGHT", 0, G.infoY)
l.line = l:CreateTexture(nil, "ARTWORK")
l.line:SetTexture(G.separator)
l.line:SetAllPoints(l)
l.title = l:CreateFontString(nil, "ARTWORK", "GameFontHighlightLeft")
l.title:SetPoint("LEFT", l, "LEFT", 5, 1)
l:SetHighlightTexture(G.highlight)
local s = l:GetHighlightTexture()
if s then
s:SetBlendMode("ADD")
s:ClearAllPoints()
s:SetPoint("TOPLEFT", l, "TOPLEFT", 0, -1)
s:SetPoint("BOTTOMRIGHT", l, "BOTTOMRIGHT", 0, 1)
end
l:SetScript("OnClick", function(self, button) S.click(self, button) end)
l:SetScript("OnEnter", function(self)
if self.kind == "friend" and FriendsFrameTooltip_Show then
FriendsFrameTooltip_Show(self)
end
end)
l:SetScript("OnLeave", function()
if FriendsTooltip then
FriendsTooltip.button = nil
FriendsTooltip:Hide()
end
end)
return l
end
-- Lays out a row for its entry kind: friend, line, header or ignore. Parts of the other
-- kinds are hidden. l: row; e: entry from entries()
local function layoutRow(l, e)
l.kind = e.kind
local isFriend, line, header = e.kind == "friend", e.kind == "line", e.kind == "header"
local ignore = e.kind == "ignore"
l:SetHeight(isFriend and G.friendRowH or G.shortRowH)
if isFriend then l.background:Show() else l.background:Hide() end
if isFriend then l.state:Show() else l.state:Hide() end
if isFriend or ignore then l.name:Show() else l.name:Hide() end
if isFriend then l.info:Show() else l.info:Hide() end
if line then l.line:Show() else l.line:Hide() end
if header then l.title:Show() else l.title:Hide() end
l:EnableMouse(isFriend or ignore)
l:UnlockHighlight()
local s = l:GetHighlightTexture()
l.name:ClearAllPoints()
if isFriend then
l.name:SetFontObject(FriendsFont_Normal)
l.name:SetPoint("TOPLEFT", l, "TOPLEFT", G.nameX, G.nameY)
l.name:SetPoint("TOPRIGHT", l, "TOPRIGHT", -4, G.nameY)
if s then
s:SetTexture(G.highlight)
s:SetVertexColor(G.highlightTint[1], G.highlightTint[2], G.highlightTint[3])
end
elseif ignore then
-- FriendsFrameIgnoreButtonTemplate: GameFontNormal at (10, 1)
l.name:SetFontObject(GameFontNormal)
l.name:SetPoint("LEFT", l, "LEFT", 10, 1)
l.name:SetPoint("RIGHT", l, "RIGHT", -4, 1)
if s then
s:SetTexture(G.ignoreHighlight)
s:SetVertexColor(1, 1, 1)
end
end
l.id = e.index
l.squelch = e.squelch
l.buttonType = isFriend and FRIENDS_BUTTON_TYPE_WOW or nil
if isFriend then
-- FriendsFrame_SetButton, for a game friend
local name, level, className, zone, connected, status = GetFriendInfo(e.index)
l.friendName, l.connected = name, connected
if connected then
l.background:SetTexture(color("FRIENDS_WOW_BACKGROUND_COLOR", { 1.0, 0.824, 0.0, 0.05 }))
if status == CHAT_FLAG_AFK then
l.state:SetTexture(G.state .. "Away")
elseif status == CHAT_FLAG_DND then
l.state:SetTexture(G.state .. "DnD")
else
l.state:SetTexture(G.state .. "Online")
end
l.name:SetText((name or "") .. ", " .. string.format(txt("FRIENDS_LEVEL_TEMPLATE"), level or 0, className or ""))
l.name:SetTextColor(color("FRIENDS_WOW_NAME_COLOR", { 0.996, 0.882, 0.361 }))
else
l.background:SetTexture(color("FRIENDS_OFFLINE_BACKGROUND_COLOR", { 0.588, 0.588, 0.588, 0.05 }))
l.state:SetTexture(G.state .. "Offline")
l.name:SetText(name or "")
l.name:SetTextColor(color("FRIENDS_GRAY_COLOR", { 0.486, 0.518, 0.541 }))
end
l.info:SetText(zone or "")
l.info:SetTextColor(color("FRIENDS_GRAY_COLOR", { 0.486, 0.518, 0.541 }))
if GetSelectedFriend() == e.index then l:LockHighlight() end
elseif ignore then
local name = GetIgnoreName(e.index)
if FriendsFrame.selectedSquelchType == SQUELCH_TYPE_IGNORE and GetSelectedIgnore() == e.index then
l:LockHighlight()
end
l.name:SetText(name or txt("UNKNOWN"))
l.name:SetTextColor(color("NORMAL_FONT_COLOR", { 1, 0.82, 0 }))
elseif header then
l.title:SetText(e.text)
end
end
-- What the list shows, in WotLK order.
local function entries()
local list = {}
if S.subTab() == 2 then
-- IgnoreList_Update: ignored players
local ignores = GetNumIgnores() or 0
if ignores > 0 then
list[#list + 1] = { kind = "header", text = txt("IGNORED") }
for i = 1, ignores do
list[#list + 1] = { kind = "ignore", index = i, squelch = SQUELCH_TYPE_IGNORE }
end
end
else
-- FriendsList_Update: online, a divider, offline
local total, online = GetNumFriends()
total, online = total or 0, online or 0
for i = 1, online do
list[#list + 1] = { kind = "friend", index = i }
end
if online > 0 and total > online then
list[#list + 1] = { kind = "line" }
end
for i = online + 1, total do
list[#list + 1] = { kind = "friend", index = i }
end
end
return list
end
local function heightOf(e)
return e.kind == "friend" and G.friendRowH or G.shortRowH
end
-- Fills the rows that fit, from S.offset (friend rows are taller).
function S.layoutList()
local list = S.content or {}
local position = S.list:GetHeight() or 0
if position <= 0 then
position = G.height + G.listY - (G.frameBoxY2 + G.listY2)
end
local total = #list
S.offset = math.max(0, math.min(S.offset or 0, total - 1))
local y, rank, n = 0, 0, S.offset + 1
while n <= total do
local h = heightOf(list[n])
if y + h > position then break end
rank = rank + 1
local l = ROWS[rank]
if not l then
l = createRow(rank)
ROWS[rank] = l
end
l:ClearAllPoints()
l:SetPoint("TOPLEFT", S.list, "TOPLEFT", 0, -y)
l:SetPoint("TOPRIGHT", S.list, "TOPRIGHT", 0, -y)
layoutRow(l, list[n])
l:Show()
y = y + h
n = n + 1
end
for i = rank + 1, #ROWS do
ROWS[i]:Hide()
end
S.visibleCount = rank
S.bar:Configure(total, rank, S.offset)
end
-- ---------------------------------------------------------------- Buttons
-- 3.3.5 has no SetShown
local function showRegion(x, yes)
if yes then x:Show() else x:Hide() end
end
-- Enables or disables a button (Activate on ForeverUI panel buttons).
local function active(b, yes)
if b.Activate then b:Activate(yes) elseif yes then b:Enable() else b:Disable() end
end
local function updateButtons()
local b = S.buttons
local ignore = S.subTab() == 2
showRegion(b.add, not ignore)
showRegion(b.message, not ignore)
showRegion(b.ignoreButton, ignore)
showRegion(b.remove, ignore)
if not ignore then
-- FriendsList_Update: Send Message only to an online friend
local selected = GetSelectedFriend() or 0
local connected = false
if selected > 0 then
connected = select(5, GetFriendInfo(selected)) and true or false
end
active(b.message, connected)
else
local index = 0
if FriendsFrame.selectedSquelchType == SQUELCH_TYPE_IGNORE then
index = GetSelectedIgnore() or 0
end
active(b.remove, index > 0)
end
end
-- ------------------------------------------------------------ Update
-- Selected Contacts sub-tab: 1 friends, 2 ignore. Kept here: written from addon code,
-- FriendsTabHeader.selectedTab would taint the client's FriendsFrame_Update, which reads it
-- and shows or hides RaidFrame (protected once Blizzard_RaidUI loads), blocked in combat.
S.selectedSubTab = 1
function S.subTab()
return S.selectedSubTab == 2 and 2 or 1
end
-- /friends and /ignore (ToggleFriendsPanel, ToggleIgnorePanel) select the client's sub-tab:
-- ours follows.
for name, id in pairs({ ToggleFriendsPanel = 1, ToggleIgnorePanel = 2 }) do
if type(_G[name]) == "function" then
hooksecurefunc(name, function()
if S.selectedSubTab ~= id then
S.selectedSubTab, S.offset = id, 0
end
if S.frame and S.frame:IsVisible() then S.update() end
end)
end
end
-- Default selection, like the client: first friend, first ignored player.
local function selectDefault()
if S.subTab() == 2 then
local ok = FriendsFrame.selectedSquelchType == SQUELCH_TYPE_IGNORE and (GetSelectedIgnore() or 0) > 0
if not ok and (GetNumIgnores() or 0) > 0 then
FriendsFrame_SelectSquelched(SQUELCH_TYPE_IGNORE, 1)
end
else
local total = GetNumFriends() or 0
if total > 0 and (GetSelectedFriend() or 0) == 0 then
FriendsFrame_SelectFriend(FRIENDS_BUTTON_TYPE_WOW, 1)
end
FriendsFrame.selectedFriend = GetSelectedFriend()
FriendsFrame.selectedFriendType = FRIENDS_BUTTON_TYPE_WOW
end
end
-- Bottom tabs follow the client's selected tab; Guild is disabled outside a guild, like
-- the client's (InGuildCheck).
function S.updateTabs()
local selected = FriendsFrame.selectedTab or 1
for _, o in ipairs(S.tabs) do
local client = _G["FriendsFrameTab" .. o.id]
local isOpen = true
if client and client.IsEnabled and o.id ~= selected then
local e = client:IsEnabled()
isOpen = (e ~= nil and e ~= false and e ~= 0)
end
o.button:SetText(txt(o.text))
selectTab(o.button, o.id == selected, isOpen)
-- secure, so sized out of combat only (their texts do not change)
if not InCombatLockdown() then
o.button:SetWidth(tabWidth(o.button))
end
end
end
function S.updateContacts()
local f = S.frame
if not f then return end
local sub = S.subTab()
f.title:SetText(txt(SUB_TABS[sub].title))
-- sub-tabs
for _, o in ipairs(S.subTabs) do
o.button:SetText(txt(o.text))
selectTab(o.button, o.id == sub, true)
o.button:SetWidth(tabWidth(o.button))
end
selectDefault()
S.content = entries()
S.layoutList()
updateButtons()
end
-- ------------------------------------------------------------ Clicks
function S.click(l, button)
if l.kind == "friend" then
if button == "RightButton" then
local name, _, _, _, connected = GetFriendInfo(l.id)
ForeverUI.UnitMenu.open(FriendsFrame_ShowDropdown, name, connected, nil, nil, nil, 1)
else
PlaySound("igMainMenuOptionCheckBoxOn")
FriendsFrame_SelectFriend(FRIENDS_BUTTON_TYPE_WOW, l.id)
FriendsFrame.selectedFriend = l.id
end
elseif l.kind == "ignore" then
PlaySound("igMainMenuOptionCheckBoxOn")
FriendsFrame_SelectSquelched(l.squelch, l.id)
end
S.update()
end
-- ------------------------------------------------------ WotLK or camelot
-- Client frames our window keeps and borrows: the right-click menu and the tooltip.
local KEPT_FRAMES = { "FriendsDropDown", "FriendsTooltip" }
-- A page can keep a client frame (the raid frame of Blizzard_RaidUI...).
function S.keep(name)
KEPT_FRAMES[#KEPT_FRAMES + 1] = name
end
-- Hides the client panel's own content, except kept frames, and stops it taking the
-- mouse.
local function suppressWotLK()
local ff = FriendsFrame
S.killedRegions = S.killedRegions or {}
for _, r in ipairs({ ff:GetRegions() }) do
if r:IsShown() then
S.killedRegions[r] = true
r:Hide()
end
end
local keep = { [S.frame] = true }
for _, n in ipairs(KEPT_FRAMES) do
local c = type(n) == "string" and _G[n] or n
if c then keep[c] = true end
end
-- A protected frame cannot be hidden in combat: in a raid, RaidFrame holds the secure
-- buttons of Blizzard_RaidUI. It is faded with alpha, which combat allows, and hidden on
-- the next pass out of combat.
local combat = InCombatLockdown and InCombatLockdown()
for _, c in ipairs({ ff:GetChildren() }) do
if not keep[c] and c:IsShown() then
if combat and c.IsProtected and c:IsProtected() then
c:SetAlpha(0)
else
c:Hide()
end
end
end
if not (combat and ff.IsProtected and ff:IsProtected()) then
ff:EnableMouse(false)
end
end
-- Pages: one per client tab. Each registers build(frame), update(), showRegion(firstShow),
-- hide() and title().
S.pages = {}
local registeredPages = {}
function S.registerPage(id, def)
registeredPages[id] = def
end
-- Returns the page of tab id, building it on first use.
local function page(id)
local pg = S.pages[id]
if pg then return pg end
local def = registeredPages[id]
if not def then return nil end
local frame = CreateFrame("Frame", "ForeverUISocialPage" .. id, S.frame)
frame:SetAllPoints(S.frame)
frame:Hide()
pg = { id = id, frame = frame, def = def }
S.pages[id] = pg
def.build(frame)
return pg
end
-- Builds a page ahead of time (the raid page, out of combat).
function S.prepare(id)
if S.frame then page(id) end
end
-- Refreshes the selected page and the tabs.
function S.update()
S.updateTabs()
local pg = S.pages[FriendsFrame.selectedTab or 1]
if pg and pg.frame:IsShown() then
if pg.def.title then S.frame.title:SetText(pg.def.title() or "") end
if pg.def.update then pg.def.update() end
end
end
-- Runs after FriendsFrame_Update, which has already set up the client screen.
-- FriendsFrame stays the client panel (ToggleFriendsFrame, micro button, Esc, panel slot,
-- selectedTab); our window, its child, hides the WotLK content and shows the page of the
-- selected tab. The page redoes what the client subframe does on show (SetWhoToUI,
-- GuildRoster...).
function S.apply()
if not S.frame or not FriendsFrame:IsShown() then return end
local selected = FriendsFrame.selectedTab or 1
-- page first: while building, it can keep a client frame
page(selected)
suppressWotLK()
-- Protected through its secure tabs: kept shown, it follows FriendsFrame
if not S.frame:IsShown() then S.frame:Show() end
for id, pg in pairs(S.pages) do
if id ~= selected and pg.frame:IsShown() then
pg.frame:Hide()
if pg.def.hide then pg.def.hide() end
end
end
local pg = page(selected)
if pg then
local wasShown = pg.frame:IsShown()
pg.frame:Show()
if pg.def.showRegion then pg.def.showRegion(not wasShown) end
end
S.update()
end
-- On close, the open page does what the client subframe does on hide (SetWhoToUI(0)...).
local function close()
for _, pg in pairs(S.pages) do
if pg.frame:IsShown() then
pg.frame:Hide()
if pg.def.hide then pg.def.hide() end
end
end
if S.popup then S.popup:Hide() end
end
-- ------------------------------------------------------ Shared parts
S.txt = txt
S.color = color
S.G = G
-- A panel button with the window's button font (GameFontNormal).
function S.button(parent, text, width, name)
return ForeverUI.CreatePanelButton(parent, text, width, G.buttonH, name, "GameFontNormal")
end
-- Column header: WhoFrameColumnHeaderTemplate, whose WotLK art camelot keeps (mainline):
-- WhoFrame-ColumnTabs in three pieces, UI-Character-Tab-Highlight in ADD.
local COLUMN_TABS = "Interface" .. SEP .. "FriendsFrame" .. SEP .. "WhoFrame-ColumnTabs"
local COLUMN_HIGHLIGHT = "Interface" .. SEP .. "PaperDollInfoFrame" .. SEP .. "UI-Character-Tab-Highlight"
S.COLUMN_H = 24
function S.createHeader(parent, name, width, text, onClick)
local b = CreateFrame("Button", name, parent)
b:SetHeight(S.COLUMN_H)
local g = b:CreateTexture(nil, "BACKGROUND")
g:SetTexture(COLUMN_TABS)
g:SetTexCoord(0, 0.078125, 0, 0.75)
g:SetWidth(5)
g:SetHeight(S.COLUMN_H)
g:SetPoint("TOPLEFT", b, "TOPLEFT", 0, 0)
local d = b:CreateTexture(nil, "BACKGROUND")
d:SetTexture(COLUMN_TABS)
d:SetTexCoord(0.90625, 0.96875, 0, 0.75)
d:SetWidth(4)
d:SetHeight(S.COLUMN_H)
d:SetPoint("TOPRIGHT", b, "TOPRIGHT", 0, 0)
local m = b:CreateTexture(nil, "BACKGROUND")
m:SetTexture(COLUMN_TABS)
m:SetTexCoord(0.078125, 0.90625, 0, 0.75)
m:SetHeight(S.COLUMN_H)
m:SetPoint("LEFT", g, "RIGHT", 0, 0)
m:SetPoint("RIGHT", d, "LEFT", 0, 0)
local fs = b:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
fs:SetPoint("LEFT", b, "LEFT", 8, 0)
fs:SetPoint("RIGHT", b, "RIGHT", -8, 0)
fs:SetJustifyH("LEFT")
b:SetFontString(fs)
b.text = fs
b:SetText(text or "")
b:SetHighlightTexture(COLUMN_HIGHLIGHT)
local s = b:GetHighlightTexture()
if s then
s:SetBlendMode("ADD")
s:ClearAllPoints()
s:SetPoint("TOPLEFT", g, "TOPLEFT", -2, 5)
s:SetPoint("BOTTOMRIGHT", d, "BOTTOMRIGHT", 2, -7)
end
b:SetWidth(width)
if onClick then
b:SetScript("OnClick", function(self)
onClick(self)
PlaySound("igMainMenuOptionCheckBoxOn")
end)
end
return b
end
-- Scrolling list with fixed-height rows: area, rows, camelot scroll bar and mouse wheel.
-- create(row, n) builds a row; populate(row, index) fills it; list:Update(total) redraws.
function S.createList(parent, name, lineHeight, create, populate)
local zone = CreateFrame("Frame", name, parent)
zone.rows = {}
zone.offset = 0
zone.total = 0
zone.lineHeight = lineHeight
zone.bar = ForeverUI.CreateScrollBar(name .. "ScrollBar", parent, zone)
function zone:VisibleCount()
local h = self:GetHeight() or 0
if h <= 0 then h = self.defaultHeight or (lineHeight * 10) end
return math.max(1, math.floor(h / lineHeight))
end
function zone:Update(total)
self.total = total or self.total
local visibleCount = self:VisibleCount()
self.offset = math.max(0, math.min(self.offset, self.total - visibleCount))
for n = 1, visibleCount do
local l = self.rows[n]
if not l then
l = CreateFrame("Button", name .. "Row" .. n, self)
l:SetHeight(lineHeight)
l:SetPoint("TOPLEFT", self, "TOPLEFT", 0, -(n - 1) * lineHeight)
l:SetPoint("TOPRIGHT", self, "TOPRIGHT", 0, -(n - 1) * lineHeight)
create(l, n)
self.rows[n] = l
end
local index = self.offset + n
if index <= self.total then
l.index = index
populate(l, index)
l:Show()
else
l.index = nil
l:Hide()
end
end
for n = visibleCount + 1, #self.rows do
self.rows[n]:Hide()
end
self.bar:Configure(self.total, visibleCount, self.offset)
-- the bar appears or goes: the right edge follows
local hasBar = self.total > visibleCount
if hasBar ~= self.hasBar then
self.hasBar = hasBar
self:PlaceAnchors()
end
end
-- Right edge follows the bar: with it, the list leaves room for it; without it, the list
-- (and the rows and headers anchored to it) reaches the edge. topLeft and
-- bottomRightPoint are { point, relativeTo, relativePoint, x, y }; noBar replaces the
-- bottom-right x when the bar is hidden.
function zone:FollowBar(topLeft, bottomRightPoint, noBar)
self.anchors = { topLeft = topLeft, bottomRightPoint = bottomRightPoint, noBar = noBar }
self:PlaceAnchors()
end
function zone:PlaceAnchors()
local a = self.anchors
if not a then return end
local h, b = a.topLeft, a.bottomRightPoint
self:ClearAllPoints()
self:SetPoint(h[1], h[2], h[3], h[4], h[5])
self:SetPoint(b[1], b[2], b[3], self.hasBar and b[4] or a.noBar, b[5])
end
zone.bar.onScroll = function(new)
zone.offset = new
zone:Update()
end
zone:EnableMouseWheel(true)
zone:SetScript("OnMouseWheel", function(self, direction)
self.offset = math.max(0, math.min(self.offset - direction, self.total - self:VisibleCount()))
self:Update()
end)
return zone
end
-- Camelot border on an edit box, ours or the client's (InputBoxTemplate: its <name>Left,
-- Middle and Right pieces are hidden).
function S.skinInput(b)
local name = b.GetName and b:GetName()
if name then
for _, side in ipairs({ "Left", "Middle", "Right" }) do
local t = _G[name .. side]
if t then t:SetAlpha(0) t:Hide() end
end
end
local g = b:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(g, "common-search-border-left", true)
g:SetWidth(8) g:SetHeight(20)
g:SetPoint("LEFT", b, "LEFT", -5, 0)
local d = b:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(d, "common-search-border-right", true)
d:SetWidth(8) d:SetHeight(20)
d:SetPoint("RIGHT", b, "RIGHT", 0, 0)
local m = b:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(m, "common-search-border-middle", true)
m:SetHeight(20)
m:SetPoint("LEFT", g, "RIGHT", 0, 0)
m:SetPoint("RIGHT", d, "LEFT", 0, 0)
b.camelotBorder = { g, m, d }
end
-- Checkbox: checkbox-minimal and checkmark-minimal, 26 x 26, like the Currency tab boxes.
function S.createCheckbox(parent, name, text)
local c = CreateFrame("CheckButton", name, parent)
c:SetWidth(26)
c:SetHeight(26)
S.skinCell(c)
c.text = c:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
c.text:SetPoint("LEFT", c, "RIGHT", 2, 1)
c.text:SetText(text or "")
return c
end
-- Camelot art on a checkbox, ours or the client's: empty box and check, without the WotLK
-- pushed and highlight states.
function S.skinCell(c)
local function place(setMethod, getMethod, atlas)
local e = ForeverUI.AtlasEntry(atlas)
if c[setMethod] then c[setMethod](c, e and e[1] or "") end
local t = c[getMethod] and c[getMethod](c)
if t then
ForeverUI.SetAtlas(t, atlas, true)
t:ClearAllPoints()
t:SetAllPoints(c)
end
return t
end
place("SetNormalTexture", "GetNormalTexture", "checkbox-minimal")
place("SetCheckedTexture", "GetCheckedTexture", "checkmark-minimal")
local d = place("SetDisabledCheckedTexture", "GetDisabledCheckedTexture", "checkmark-minimal")
if d then d:SetDesaturated(true) end
-- The pushed texture replaces the normal one, so it carries the same box, or the outline
-- vanishes during the click. Only the check comes and goes.
place("SetPushedTexture", "GetPushedTexture", "checkbox-minimal")
if c.SetHighlightTexture then c:SetHighlightTexture("") end
end
-- Side popup: member details, guild info, log, saved instances. One at a time, right of
-- the window, like GuildFramePopup_Show; the same portrait-less frame as arena team
-- details.
function S.createPopup(name, width, height)
local a = CreateFrame("Frame", name, S.frame)
a:SetWidth(width)
a:SetHeight(height)
a:SetPoint("TOPLEFT", S.frame, "TOPRIGHT", 12, 0)
a:SetFrameLevel(S.frame:GetFrameLevel() + 30)
a:EnableMouse(true)
a:Hide()
ForeverUI.SetPanelArt(a, { topLeftCorner = "ui-frame-metal-cornertopleft", level = 5 })
local metal = a.foreverSkinLayer or a
local banner = CreateFrame("Frame", nil, a)
banner:SetAllPoints(a)
banner:SetFrameLevel(metal:GetFrameLevel() + 1)
a.title = banner:CreateFontString(nil, "OVERLAY", "GameFontNormal")
a.title:SetPoint("TOP", a, "TOP", 0, -6)
a.title:SetWidth(width - 60)
local closeButton = CreateFrame("Button", nil, a)
closeButton:SetWidth(G.closeButton)
closeButton:SetHeight(G.closeButton)
closeButton:SetFrameLevel(metal:GetFrameLevel() + 2)
closeButton:SetPoint("TOPRIGHT", a, "TOPRIGHT", 1, 0)
for _, state in ipairs({
{ "SetNormalTexture", "GetNormalTexture", "redbutton-exit" },
{ "SetPushedTexture", "GetPushedTexture", "redbutton-exit-pressed" },
{ "SetHighlightTexture", "GetHighlightTexture", "redbutton-highlight" },
}) do
local e = ForeverUI.AtlasEntry(state[3])
closeButton[state[1]](closeButton, e and e[1] or "")
local t = closeButton[state[2]](closeButton)
if t then
ForeverUI.SetAtlas(t, state[3], true)
t:ClearAllPoints()
t:SetAllPoints(closeButton)
if state[3] == "redbutton-highlight" then t:SetBlendMode("ADD") end
end
end
closeButton:SetScript("OnClick", function() a:Hide() end)
a.closeButton = closeButton
a:HookScript("OnShow", function(self)
if S.popup and S.popup ~= self then S.popup:Hide() end
S.popup = self
PlaySound("igSpellBookOpen")
end)
a:HookScript("OnHide", function(self)
if S.popup == self then S.popup = nil end
PlaySound("igSpellBookClose")
end)
S.annexes = S.annexes or {}
table.insert(S.annexes, a)
return a
end
-- ------------------------------------------------------------ Assembly
local function build()
if S.frame or not FriendsFrame then return end
local f = CreateFrame("Frame", "ForeverUISocialFrame", FriendsFrame)
f:SetWidth(G.width)
f:SetHeight(G.height)
f:SetPoint("TOPLEFT", FriendsFrame, "TOPLEFT", 0, 0)
f:SetFrameLevel(FriendsFrame:GetFrameLevel() + 1)
f:EnableMouse(true)
-- FriendsFrame (384 x 512) catches no click outside our window. Set now, before the secure
-- tabs protect it: on a protected frame in combat, suppressWotLK cannot.
FriendsFrame:EnableMouse(false)
S.frame = f
buildFrame(f)
-- Contacts page: sub-tabs, inset, list and buttons.
S.registerPage(1, {
build = function() end,
title = function() return txt(SUB_TABS[S.subTab()].title) end,
update = function() S.updateContacts() end,
})
local p1 = page(1)
local contacts = p1.frame
local frameBox = ForeverUI.CreateInset(contacts, "ForeverUISocialInset")
frameBox:SetPoint("TOPLEFT", f, "TOPLEFT", G.frameBoxX1, G.frameBoxY1)
frameBox:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", G.frameBoxX2, G.frameBoxY2)
S.frameBox = frameBox
local list = CreateFrame("Frame", "ForeverUISocialList", contacts)
list:SetPoint("TOPLEFT", f, "TOPLEFT", G.listX, G.listY)
list:SetPoint("BOTTOMRIGHT", frameBox, "BOTTOMRIGHT", G.listX2, G.listY2)
list:EnableMouseWheel(true)
list:SetScript("OnMouseWheel", function(_, direction)
S.offset = math.max(0, math.min((S.offset or 0) - direction, #(S.content or {}) - (S.visibleCount or 0)))
S.layoutList()
end)
S.list = list
S.bar = ForeverUI.CreateScrollBar("ForeverUISocialScrollBar", contacts, list)
S.bar.onScroll = function(new)
S.offset = new
S.layoutList()
end
-- without the bar the list reaches the inset edge, like the Who and Guild lists; rows,
-- anchored on both sides, follow
S.bar.onVisibility = function(hasBar)
list:SetPoint("BOTTOMRIGHT", frameBox, "BOTTOMRIGHT", hasBar and G.listX2 or G.listX2NoBar, G.listY2)
end
-- bottom tabs, on the window
S.tabs = {}
local previous
for i, def in ipairs(TABS) do
-- Secure: a click on our tab clicks the client's (SecureTemplates "click" action), whose
-- PanelTemplates_Tab_OnClick and FriendsFrame_OnShow (sound included) then run as the
-- client's own code. Called from ours, they wrote FriendsFrame.selectedTab tainted, and
-- FriendsFrame_ShowSubFrame shows or hides RaidFrame, protected once Blizzard_RaidUI
-- loads: blocked in combat, and so was the next opening of the window in combat.
local b = createTab(f, "ForeverUISocialTab" .. i, false, "SecureActionButtonTemplate")
b.secureTab = true
b:SetAttribute("type", "click")
b:SetAttribute("clickbutton", _G["FriendsFrameTab" .. def.id])
if previous then
b:SetPoint("TOPLEFT", previous, "TOPRIGHT", G.tabGap, 0)
else
b:SetPoint("TOPLEFT", f, "BOTTOMLEFT", G.firstTabX, G.firstTabY)
end
-- sized now, out of combat: in combat a secure tab keeps its size
b:SetText(txt(def.text))
b:SetWidth(tabWidth(b))
S.tabs[i] = { id = def.id, text = def.text, button = b }
previous = b
end
-- sub-tabs: Friends and Ignore, state kept here (S.selectedSubTab)
S.subTabs = {}
previous = nil
for i, def in ipairs(SUB_TABS) do
local b = createTab(contacts, "ForeverUISocialSubTab" .. i, true)
b:SetFrameLevel(f:GetFrameLevel() + 2)
if previous then
b:SetPoint("TOPLEFT", previous, "TOPRIGHT", G.subTabGap, 0)
else
b:SetPoint("TOPLEFT", f, "TOPLEFT", G.subTabX, G.subTabY)
end
b:SetScript("OnClick", function()
PlaySound("igMainMenuOptionCheckBoxOn")
S.selectedSubTab = def.id
S.offset = 0
-- the client's Friends sub-tab asks the server for the list (FriendsFrame_Update)
if def.id == 1 then ShowFriends() end
S.update()
end)
S.subTabs[i] = { id = def.id, text = def.text, button = b }
previous = b
end
-- buttons, using the client's handlers
local b = {}
local function button(text, width)
return ForeverUI.CreatePanelButton(contacts, text, width, G.buttonH, nil, "GameFontNormal")
end
b.add = button(txt("ADD_FRIEND"), G.buttonW)
b.add:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", G.buttonLeft, G.buttonBottom)
b.add:SetScript("OnClick", function(self) FriendsFrameAddFriendButton_OnClick(self) end)
b.message = button(txt("SEND_MESSAGE"), G.buttonW)
b.message:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", G.buttonRight, G.buttonBottom)
b.message:SetScript("OnClick", function(self) FriendsFrameSendMessageButton_OnClick(self) end)
b.ignoreButton = button(txt("IGNORE_PLAYER"), G.buttonW)
b.ignoreButton:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", G.buttonLeft, G.buttonBottom)
b.ignoreButton:SetScript("OnClick", function()
-- OnClick of FriendsFrameIgnorePlayerButton, from the client XML
if UnitCanCooperate("player", "target") then
AddIgnore(UnitName("target"))
else
StaticPopup_Show("ADD_IGNORE")
end
end)
b.remove = button(txt("REMOVE_PLAYER"), G.buttonW)
b.remove:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", G.buttonRight, G.buttonBottom)
b.remove:SetScript("OnClick", function(self) FriendsFrameUnsquelchButton_OnClick(self) end)
S.buttons = b
-- left shown: the secure tabs protect the window, which could not be shown in combat; as a
-- child of FriendsFrame it appears and goes with it
end
build()
if S.frame then
hooksecurefunc("FriendsFrame_Update", S.apply)
FriendsFrame:HookScript("OnShow", S.apply)
FriendsFrame:HookScript("OnHide", close)
-- movable by its title; raised when opened or clicked
ForeverUI.WindowStack.makeMovable(S.frame, S.frame.banner, "social")
ForeverUI.WindowStack.register("social", FriendsFrame, function()
local z = { S.frame }
for _, a in ipairs(S.annexes or {}) do z[#z + 1] = a end
return z
end)
local listener = CreateFrame("Frame")
for _, ev in ipairs({ "FRIENDLIST_UPDATE", "IGNORELIST_UPDATE",
"PARTY_MEMBERS_CHANGED", "PLAYER_GUILD_UPDATE" }) do
listener:RegisterEvent(ev)
end
listener:SetScript("OnEvent", function()
if S.frame:IsVisible() and (FriendsFrame.selectedTab or 1) == 1 then S.update() end
end)
end