ca2b78e217
/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>
306 lines
12 KiB
Lua
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
|