Files
moon-well-ui/data/addon/ForeverUI/TabardFrame.lua
T
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

306 lines
12 KiB
Lua

-- ForeverUI: the guild tabard window in camelot's style (camelot: mainline/tabardframe.xml).
-- The client's TabardFrame and TabardModel are kept, resized, re-anchored and re-skinned; none
-- of their frames changes parent. The merchant portrait sits inside the metal ring, as in the
-- Social window. In 3.3.5 two frames at the same level draw in an undefined order, so the
-- insets and the gold edge are regions of the window itself (BACKGROUND, BORDER, ARTWORK),
-- under the tabard frame and the emblem (OVERLAY). Empty frames serve as anchors.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local L = ForeverUI.L
local SEP = string.char(92)
local P = {
width = 338, height = 424,
rock = "interface" .. SEP .. "ForeverUI" .. SEP .. "framegeneral" .. SEP .. "ui-background-rock",
money = "Interface" .. SEP .. "ForeverUI" .. SEP .. "common" .. SEP .. "moneyframe",
portraitSide = 60, portraitX = -5, portraitY = 7,
closeButton = 24, closeButtonX = -2, closeButtonY = 1,
}
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 },
}
-- camelot anchors: { region, point, relative frame, relative point, x, y }
local ANCHORS = {
{ "TabardFrameOuterFrameTopLeft", "TOPLEFT", "TabardFrame", "TOPLEFT", 8, -63 },
{ "TabardFrameGreetingText", "TOP", "TabardFrame", "TOP", 15, -28 },
-- model: left half of the inset (see model framing below), camelot's bottom offset
{ "TabardModel", "BOTTOMLEFT", "TabardFrame", "BOTTOMLEFT", 4, 38 },
{ "TabardCharacterModelRotateLeftButton", "BOTTOMLEFT", "TabardFrame", "BOTTOMLEFT", 14, 33 },
{ "TabardFrameCustomizationBorder", "BOTTOMRIGHT", "TabardFrame", "BOTTOMRIGHT", 26, -28 },
{ "TabardFrameMoneyFrame", "BOTTOMRIGHT", "TabardFrame", "BOTTOMLEFT", 175, 8 },
{ "TabardFrameAcceptButton", "CENTER", "TabardFrame", "TOPLEFT", 213, -409 },
{ "TabardFrameCancelButton", "CENTER", "TabardFrame", "TOPLEFT", 294, -409 },
}
-- model width: left half of the inset, from 4 to the customization panel (154); its height
-- stays the client's (317)
local MODEL_WIDTH = 150
local T = {}
ForeverUI.TabardFrame = T
-- ButtonFrameTemplate shell (same values as the Social window)
local function wrap(f)
local rock = f:CreateTexture(nil, "BACKGROUND")
rock:SetTexture(P.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)
T.rock = rock
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 c = {}
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)
c[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", c.topLeft, "TOPRIGHT", "TOPRIGHT", c.topRight, "TOPLEFT")
edge("_ui-frame-metal-edgebottom", "BOTTOMLEFT", c.bottomLeft, "BOTTOMRIGHT", "BOTTOMRIGHT", c.bottomRight, "BOTTOMLEFT")
edge("!ui-frame-metal-edgeleft", "TOPLEFT", c.topLeft, "BOTTOMLEFT", "BOTTOMLEFT", c.bottomLeft, "TOPLEFT")
edge("!ui-frame-metal-edgeright", "TOPRIGHT", c.topRight, "BOTTOMRIGHT", "BOTTOMRIGHT", c.bottomRight, "TOPRIGHT")
T.metal = metal
-- merchant portrait, inside the ring
local portraitFrame = CreateFrame("Frame", nil, f)
portraitFrame:SetAllPoints(f)
portraitFrame:SetFrameLevel(f:GetFrameLevel() + 19)
T.portrait = portraitFrame:CreateTexture(nil, "OVERLAY")
T.portrait:SetWidth(P.portraitSide)
T.portrait:SetHeight(P.portraitSide)
T.portrait:SetPoint("TOPLEFT", f, "TOPLEFT", P.portraitX, P.portraitY)
-- merchant name, above the metal (TabardFrame_OnEvent fills the client text on open)
local banner = CreateFrame("Frame", nil, f)
banner:SetAllPoints(f)
banner:SetFrameLevel(f:GetFrameLevel() + 21)
T.name = banner:CreateFontString(nil, "OVERLAY", "GameFontNormal")
T.name:SetWidth(109)
T.name:SetHeight(16)
T.name:SetPoint("CENTER", f, "CENTER", 6, 202)
end
-- client close button, skinned as camelot's red button
local function skinCloseButton(closeButton, f)
closeButton:ClearAllPoints()
closeButton:SetPoint("TOPRIGHT", f, "TOPRIGHT", P.closeButtonX, P.closeButtonY)
closeButton:SetWidth(P.closeButton)
closeButton:SetHeight(P.closeButton)
closeButton:SetFrameLevel(f:GetFrameLevel() + 22)
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
end
-- Empty frame used only for geometry.
local function createGuideFrame(f, name)
local r = CreateFrame("Frame", name, f)
r:EnableMouse(false)
return r
end
-- InsetFrameTemplate (same art as ForeverUI.DecorateInset), drawn as regions of f over the
-- guide frame r
local MARBLE = "interface" .. SEP .. "ForeverUI" .. SEP .. "framegeneral" .. SEP .. "ui-background-marble"
local function drawFrameBox(f, r)
local background = f:CreateTexture(nil, "BORDER")
background:SetTexture(MARBLE, true)
if background.SetHorizTile then background:SetHorizTile(true) background:SetVertTile(true) end
background:SetAllPoints(r)
r.background = background
local function corner(atlas, point, y)
local t = f:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(t, atlas)
t:SetPoint(point, r, point, 0, y or 0)
return t
end
local topLeft = corner("ui-frame-innertopleft", "TOPLEFT")
local topRight = corner("ui-frame-innertopright", "TOPRIGHT")
local bottomLeft = corner("ui-frame-innerbotleftcorner", "BOTTOMLEFT", -1)
local bottomRight = corner("ui-frame-innerbotright", "BOTTOMRIGHT", -1)
local function edge(atlas, a1, c1, r1, a2, c2, r2)
local t = f:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(t, atlas, true)
t:SetPoint(a1, c1, r1)
t:SetPoint(a2, c2, r2)
return t
end
edge("_ui-frame-innertoptile", "TOPLEFT", topLeft, "TOPRIGHT", "TOPRIGHT", topRight, "TOPLEFT"):SetHeight(3)
edge("_ui-frame-innerbottile", "BOTTOMLEFT", bottomLeft, "BOTTOMRIGHT", "BOTTOMRIGHT", bottomRight, "BOTTOMLEFT"):SetHeight(3)
edge("!ui-frame-innerlefttile", "TOPLEFT", topLeft, "BOTTOMLEFT", "BOTTOMLEFT", bottomLeft, "TOPLEFT"):SetWidth(3)
edge("!ui-frame-innerrighttile", "TOPRIGHT", topRight, "BOTTOMRIGHT", "BOTTOMRIGHT", bottomRight, "TOPRIGHT"):SetWidth(3)
end
-- Money area: inset and gold edge (ThinGoldEdgeTemplate, three pieces)
local function skinMoney(f)
local frameBox = createGuideFrame(f, "ForeverUITabardMoneyInset")
frameBox:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 4, 4)
frameBox:SetPoint("TOPRIGHT", f, "BOTTOMLEFT", 170, 25)
drawFrameBox(f, frameBox)
T.moneyFrameBox = frameBox
local edge = createGuideFrame(f, "ForeverUITabardMoneyBg")
edge:SetPoint("TOPRIGHT", f, "BOTTOMLEFT", 166, 24)
edge:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 7, 6)
edge.pieces = {}
local function piece(u1, u2, v1, v2)
local t = f:CreateTexture(nil, "ARTWORK")
table.insert(edge.pieces, t)
t:SetTexture(P.money)
t:SetTexCoord(u1, u2, v1, v2)
return t
end
local g = piece(0.953125, 0.9921875, 0, 0.296875)
g:SetWidth(7)
g:SetPoint("TOPLEFT", edge, "TOPLEFT")
g:SetPoint("BOTTOMLEFT", edge, "BOTTOMLEFT")
local d = piece(0, 0.0546875, 0, 0.296875)
d:SetWidth(7)
d:SetPoint("TOPRIGHT", edge, "TOPRIGHT")
d:SetPoint("BOTTOMRIGHT", edge, "BOTTOMRIGHT")
local m = piece(0, 0.9921875, 0.3125, 0.609375)
m:SetPoint("TOPLEFT", g, "TOPRIGHT")
m:SetPoint("BOTTOMRIGHT", d, "BOTTOMLEFT")
T.moneyBorder = edge
end
function T.applySkin()
local f = TabardFrame
if not f or f.foreverSkinApplied then return end
f.foreverSkinApplied = true
f:SetWidth(P.width)
f:SetHeight(P.height)
f:SetHitRectInsets(0, 0, 0, 0)
-- gone in camelot: the WotLK frame and its large background
for _, r in ipairs({ f:GetRegions() }) do
if r.GetTexture and r:GetObjectType() == "Texture" then
local path = string.lower(tostring(r:GetTexture() or ""))
if string.find(path, "ui-character-general", 1, true) or string.find(path, "ui-classtrainer-bot", 1, true) then
r:Hide()
end
end
end
if TabardFrameBackground then TabardFrameBackground:Hide() end
if TabardFramePortrait then TabardFramePortrait:Hide() end
if TabardFrameNameText then TabardFrameNameText:Hide() end
wrap(f)
local frameBox = createGuideFrame(f, "ForeverUITabardInset")
frameBox:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -60)
frameBox:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -6, 26)
drawFrameBox(f, frameBox)
T.frameBox = frameBox
skinMoney(f)
if TabardFrameCloseButton then skinCloseButton(TabardFrameCloseButton, f) end
for _, p in ipairs(ANCHORS) do
local r = _G[p[1]]
if r then
r:ClearAllPoints()
r:SetPoint(p[2], _G[p[3]], p[4], p[5], p[6])
end
end
if TabardModel then TabardModel:SetWidth(MODEL_WIDTH) end
end
-- Model framing. The client offers SetCamera, SetPosition(depth, lateral, height) and
-- SetModelScale; the engine resets them after SetUnit, so they are re-applied every frame for
-- 1.5 s after opening, as for the character sheet. Camera 0 is the portrait camera (it aims
-- at the face, drawn bottom left); camera 1 is the per-race full-body camera, which centers
-- the whole character whatever its size. No position offset: the model frame itself takes
-- the left half of the inset.
local SETTING = { camera = 1, position = { 0, 0, 0 }, scale = nil }
T.setting = { camera = SETTING.camera, position = SETTING.position, scale = SETTING.scale }
local CATCHUP = 1.5
local function applySetting()
local m = TabardModel
if not m then return end
local r = T.setting
if r.camera and m.SetCamera then m:SetCamera(r.camera) end
if r.scale and m.SetModelScale then m:SetModelScale(r.scale) end
if r.position and m.SetPosition then m:SetPosition(r.position[1], r.position[2], r.position[3]) end
end
T.applySetting = applySetting
local recheck = CreateFrame("Frame")
recheck:Hide()
recheck.rest = 0
recheck:SetScript("OnUpdate", function(self, elapsed)
applySetting()
self.rest = self.rest - (elapsed or 0)
if self.rest <= 0 then self:Hide() end
end)
T.recheck = recheck
-- Re-apply the model setting every frame for CATCHUP seconds.
local function catchUp()
recheck.rest = CATCHUP
recheck:Show()
end
-- On OPEN_TABARD_FRAME the 3.3.5 client calls TabardModel:SetUnit("player") before
-- ShowUIPanel, so the model frames itself on a hidden window and ends up bottom left. As
-- camelot does, SetUnit is called again once the window is shown (next frame), then the
-- current tabard is restored as the client does after SetUnit (InitializeTabardColors,
-- TabardFrame_UpdateTextures).
local reframe = CreateFrame("Frame")
reframe:Hide()
reframe:SetScript("OnUpdate", function(self)
self:Hide()
if not (TabardFrame and TabardFrame:IsShown() and TabardModel) then return end
TabardModel:SetUnit("player")
if TabardModel.InitializeTabardColors then TabardModel:InitializeTabardColors() end
if TabardFrame_UpdateTextures then TabardFrame_UpdateTextures() end
if TabardFrame_UpdateButtons then TabardFrame_UpdateButtons() end
catchUp()
end)
T.reframe = reframe
-- On open: merchant portrait and name (just set by TabardFrame_OnEvent on the client
-- regions), then reframe the model
function T.open()
SetPortraitTexture(T.portrait, "npc")
T.name:SetText(TabardFrameNameText and TabardFrameNameText:GetText() or UnitName("npc"))
reframe:Show()
end
if TabardFrame then
T.applySkin()
TabardFrame:HookScript("OnShow", T.open)
end