5842eb7a2b
Traduction - Identifiants en anglais dans tout le module : addon, ecrans d'accueil, outils Python, banc d'essai et patcheur C#. - Fichiers au nom francais renommes (Gabarits.lua -> Templates.lua, Textes_*.lua -> Texts_*.lua, deployer.py -> deploy.py, tools/patcheur -> tools/patcher, etc.). - Commandes /fui et options de deploy.py en anglais (--check, --server, --remove-dbc). - Cles des variables sauvegardees et de data/dbc/statistics.json traduites, sans reprise des anciennes cles. - API de la micro-barre : ForeverUI.AddMicroButton(def) avec les champs name, atlasSet, after, tooltip, onClick, ready, et ForeverUI.UpdateMicro(name, isOpen). - Commentaires reecrits en anglais, courts, sans dates ni historique ; les fonctions au nom peu parlant et leurs parametres sont expliques. Code mort - Exports, fonctions et variables jamais lus retires. - Retires a la demande des modules : ForeverUI.PvPJauge, les exports FondEntete / TuilerEntete / Polices de TradeSkill, l'art legacy-up-c60, honorfill(half)blue et inv_misc_book_09, leurs entrees d'atlas et les parties d'outils correspondantes. Verifications : liaisons des noms comparees avant et apres, banc vert, chemins de textures inchanges, generateurs reproduisant les fichiers, patcheur identique octet pour octet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1132 lines
40 KiB
Lua
1132 lines
40 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: hides the client panel, like the client's own.
|
|
local closeButton = CreateFrame("Button", "ForeverUISocialCloseButton", f)
|
|
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:SetScript("OnClick", function() HideUIPanel(FriendsFrame) end)
|
|
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
|
|
local function createTab(parent, name, atTop)
|
|
local b = CreateFrame("Button", name, parent)
|
|
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 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.
|
|
function S.subTab()
|
|
local n = FriendsTabHeader and FriendsTabHeader.selectedTab or 1
|
|
if n ~= 2 then n = 1 end
|
|
return n
|
|
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)
|
|
o.button:SetWidth(tabWidth(o.button))
|
|
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()
|
|
S.frame:Show()
|
|
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)
|
|
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
|
|
local b = createTab(f, "ForeverUISocialTab" .. i, false)
|
|
if previous then
|
|
b:SetPoint("TOPLEFT", previous, "TOPRIGHT", G.tabGap, 0)
|
|
else
|
|
b:SetPoint("TOPLEFT", f, "BOTTOMLEFT", G.firstTabX, G.firstTabY)
|
|
end
|
|
b:SetScript("OnClick", function()
|
|
-- the client tab does the rest: PanelTemplates_Tab_OnClick, FriendsFrame_OnShow, and
|
|
-- closing the guild frame
|
|
local client = _G["FriendsFrameTab" .. def.id]
|
|
if client and client:GetScript("OnClick") then
|
|
client:GetScript("OnClick")(client, "LeftButton")
|
|
else
|
|
PanelTemplates_SetTab(FriendsFrame, def.id)
|
|
FriendsFrame_OnShow()
|
|
end
|
|
PlaySound("igCharacterInfoTab")
|
|
end)
|
|
S.tabs[i] = { id = def.id, text = def.text, button = b }
|
|
previous = b
|
|
end
|
|
|
|
-- sub-tabs: Friends and Ignore, state kept by the client
|
|
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()
|
|
PanelTemplates_SetTab(FriendsTabHeader, def.id)
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
S.offset = 0
|
|
FriendsFrame_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
|
|
|
|
f:Hide()
|
|
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
|
|
|
|
-- Debug: /fui social
|
|
function ForeverUI.SocialDebug()
|
|
local say = function(t) DEFAULT_CHAT_FRAME:AddMessage("|cff66ccffForeverUI|r " .. t) end
|
|
if not S.frame then
|
|
say(L.SOCIAL_DEBUG_NOT_BUILT)
|
|
return
|
|
end
|
|
local total, online = GetNumFriends()
|
|
say(string.format(L.SOCIAL_DEBUG_STATE,
|
|
tostring(FriendsFrame.selectedTab), tostring(FriendsTabHeader and FriendsTabHeader.selectedTab),
|
|
tostring(S.frame:IsShown()), tostring(FriendsFrame:IsMouseEnabled())))
|
|
say(string.format(L.SOCIAL_DEBUG_FRIENDS,
|
|
tostring(total), tostring(online), tostring(GetSelectedFriend()), tostring(GetNumIgnores()),
|
|
tostring(GetSelectedIgnore())))
|
|
say(string.format(L.SOCIAL_DEBUG_LIST,
|
|
#(S.content or {}), S.visibleCount or 0, S.offset or 0))
|
|
end
|