Files
moon-well-ui/data/addon/ForeverUI/TabardFrame.lua
T
Adrestie 2c94ea6bc9 PNJ etapes 1-2, barres selon le contenu, liste des donjons, tabard
- PNJ (NpcDialog.lua, nouveau) : dialogue et quete, livres et lettres,
  petition, registre de guilde a la DA de camelot (parchemin
  QuestBG-Parchment, portrait, croix, places de camelot, contours de
  qualite des objets).
- Barres de defilement : chacune cachee quand rien ne defile, et sans
  elle le contenu laisse a droite la marge qu'il a a gauche (courrier et
  papeterie, banque de guilde, hotel des ventes, reputation, competences,
  monnaies, contacts, guilde, macros, reglages, chercheur, dialogues des
  PNJ) ; ecrans d'accueil : selection des personnages (cartes centrees),
  royaumes, AddOns, creation.
- Chercheur de donjon : la liste des donjons reprend la liste a
  categories des raids (en-tetes de la feuille, cases de camelot),
  l'en-tete gardant sa case "tout cocher".
- Tabard : camera 1 et modele dans la moitie gauche de l'encadre, cadrage
  independant de la race (gnome).
- Cabine : portrait a 48 (1, 1.5) ; README a jour (fenetres secondaires,
  commerce).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:48:43 +02:00

411 lines
18 KiB
Lua

-- ForeverUI : la fenetre de creation du tabard de guilde, facon camelot
-- (demande de l'utilisateur, 2026-09-26).
--
-- RELEVE -- camelot charge blizzard_uipanels_game/mainline/tabardframe.xml et
-- .lua ([Family], pas de variante camelot). La fenetre garde les images de
-- WotLK (le cadre du tabard TabardFrameOuterFrame, le panneau de
-- personnalisation, les etiquettes CharacterCreate-LabelFrame, les fleches,
-- la rotation) ; ce qui change :
-- enveloppe ButtonFrameTemplate, 338 x 424 (PortraitFrameBaseTemplate) :
-- fond UI-Background-Rock (2, -21 / -2, 2), stries (6, -21 /
-- -2, -21), metal a portrait, croix rouge 24 x 24 a TOPRIGHT
-- (-2, 1), encadre InsetFrameTemplate (4, -60 / -6, 26) ;
-- plus de grand fond TabardFrameBackground ni de cadre de
-- WotLK (UI-Character-General-*, UI-ClassTrainer-Bot*)
-- places (valeurs de camelot, celles de WotLK entre parentheses)
-- cadre du tabard TOPLEFT (8, -63) ((19, -73))
-- nom du marchand CENTER (6, 202) ((6, 232))
-- accueil TOP (15, -28) ((10, -39))
-- modele BOTTOM (0, 38) ((-14, 114))
-- [ici BOTTOMLEFT (4, 38), 150 de large : voir LE CADRAGE
-- DU MODELE]
-- rotation BOTTOMLEFT (14, 33) ((26, 110))
-- personnalisation BOTTOMRIGHT (26, -28) ((-9, 50))
-- argent BOTTOMRIGHT sur BOTTOMLEFT (175, 8) ((183, 86))
-- Accept / Cancel CENTER sur TOPLEFT (213 / 294, -409)
-- ((224 / 305, -420))
-- argent un encadre InsetFrameTemplate de BOTTOMLEFT (4, 4) a
-- TOPRIGHT sur BOTTOMLEFT (170, 25), et le bord dore
-- ThinGoldEdgeTemplate (Interface\Common\Moneyframe, trois
-- morceaux) de BOTTOMLEFT (7, 6) a TOPRIGHT sur BOTTOMLEFT
-- (166, 24)
-- Le reste -- l'embleme en filigrane, le cout, les cinq reglages -- garde les
-- memes ancres dans les deux clients : il suit le cadre.
--
-- ECART : camelot pose le portrait du marchand a (7, -6) en BACKGROUND
-- (TabardFramePortrait), sous l'anneau de metal ; le cadre a portrait, lui, a
-- son anneau a (-5, 7). Le portrait est pose ici DANS L'ANNEAU (60 x 60 a
-- (-5, 7)), comme la fenetre Social.
--
-- La fenetre reste celle du client (TabardFrame, TabardModel et leurs
-- fonctions) : on la retaille, on replace ses pieces, on l'habille ; aucun de
-- ses cadres ne change de parent.
--
-- LES COUCHES. Camelot pose ses encadres en cadres fils au niveau de la
-- fenetre (useParentLevel) ; en 3.3.5, deux cadres de meme niveau se dessinent
-- dans un ordre indetermine, et l'encadre pourrait couvrir le cadre du tabard
-- et l'embleme en filigrane, qui sont des regions de la fenetre. L'encadre et
-- le bord dore sont donc des REGIONS de la fenetre elle-meme : roche en
-- BACKGROUND, marbre en BORDER, liseres et bord dore en ARTWORK, sous le cadre
-- du tabard et l'embleme (OVERLAY). Des cadres vides servent de reperes.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local L = ForeverUI.L
local SEP = string.char(92)
local P = {
largeur = 338, hauteur = 424,
roche = "interface" .. SEP .. "ForeverUI" .. SEP .. "framegeneral" .. SEP .. "ui-background-rock",
argent = "Interface" .. SEP .. "ForeverUI" .. SEP .. "common" .. SEP .. "moneyframe",
portraitCote = 60, portraitX = -5, portraitY = 7,
croix = 24, croixX = -2, croixY = 1,
}
local METAL = {
{ cle = "hg", nom = "ui-frame-portraitmetal-cornertopleft", point = "TOPLEFT", x = -13, y = 16 },
{ cle = "hd", nom = "ui-frame-metal-cornertopright", point = "TOPRIGHT", x = 2, y = 16 },
{ cle = "bg", nom = "ui-frame-metal-cornerbottomleft", point = "BOTTOMLEFT", x = -13, y = -8 },
{ cle = "bd", nom = "ui-frame-metal-cornerbottomright", point = "BOTTOMRIGHT", x = 2, y = -8 },
}
-- les places de camelot : { region, point, relatif, point relatif, x, y }
local PLACES = {
{ "TabardFrameOuterFrameTopLeft", "TOPLEFT", "TabardFrame", "TOPLEFT", 8, -63 },
{ "TabardFrameGreetingText", "TOP", "TabardFrame", "TOP", 15, -28 },
-- le modele : la moitie gauche de l'encadre (voir LE CADRAGE DU MODELE),
-- au bas et a la hauteur de camelot
{ "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 },
}
-- la largeur du modele : la moitie gauche de l'encadre, de 4 au panneau de
-- personnalisation (154) ; sa hauteur reste celle du client (317)
local MODELE_LARGEUR = 150
local T = {}
ForeverUI.TabardFrame = T
-- l'enveloppe de ButtonFrameTemplate (les memes valeurs que la fenetre Social)
local function envelopper(f)
local roche = f:CreateTexture(nil, "BACKGROUND")
roche:SetTexture(P.roche, true)
if roche.SetHorizTile then roche:SetHorizTile(true) roche:SetVertTile(true) end
roche:SetPoint("TOPLEFT", f, "TOPLEFT", 2, -21)
roche:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -2, 2)
T.roche = roche
local stries = f:CreateTexture(nil, "BORDER")
ForeverUI.SetAtlas(stries, "_ui-frame-toptilestreaks", true)
stries:SetHeight(43)
stries:SetPoint("TOPLEFT", f, "TOPLEFT", 6, -21)
stries:SetPoint("TOPRIGHT", f, "TOPRIGHT", -2, -21)
local metal = CreateFrame("Frame", nil, f)
metal:SetAllPoints(f)
metal:SetFrameLevel(f:GetFrameLevel() + 20)
local c = {}
for _, coin in ipairs(METAL) do
local t = metal:CreateTexture(nil, "OVERLAY")
ForeverUI.SetAtlas(t, coin.nom)
t:SetPoint(coin.point, metal, coin.point, coin.x, coin.y)
c[coin.cle] = t
end
local function bord(nom, a1, c1, r1, a2, c2, r2)
local t = metal:CreateTexture(nil, "OVERLAY")
ForeverUI.SetAtlas(t, nom)
t:SetPoint(a1, c1, r1)
t:SetPoint(a2, c2, r2)
end
bord("_ui-frame-metal-edgetop", "TOPLEFT", c.hg, "TOPRIGHT", "TOPRIGHT", c.hd, "TOPLEFT")
bord("_ui-frame-metal-edgebottom", "BOTTOMLEFT", c.bg, "BOTTOMRIGHT", "BOTTOMRIGHT", c.bd, "BOTTOMLEFT")
bord("!ui-frame-metal-edgeleft", "TOPLEFT", c.hg, "BOTTOMLEFT", "BOTTOMLEFT", c.bg, "TOPLEFT")
bord("!ui-frame-metal-edgeright", "TOPRIGHT", c.hd, "BOTTOMRIGHT", "BOTTOMRIGHT", c.bd, "TOPRIGHT")
T.metal = metal
-- le portrait du marchand, dans l'anneau
local cadrePortrait = CreateFrame("Frame", nil, f)
cadrePortrait:SetAllPoints(f)
cadrePortrait:SetFrameLevel(f:GetFrameLevel() + 19)
T.portrait = cadrePortrait:CreateTexture(nil, "OVERLAY")
T.portrait:SetWidth(P.portraitCote)
T.portrait:SetHeight(P.portraitCote)
T.portrait:SetPoint("TOPLEFT", f, "TOPLEFT", P.portraitX, P.portraitY)
-- le nom du marchand, au-dessus du metal (le texte du client lui sert de
-- source : TabardFrame_OnEvent le remplit a l'ouverture)
local bandeau = CreateFrame("Frame", nil, f)
bandeau:SetAllPoints(f)
bandeau:SetFrameLevel(f:GetFrameLevel() + 21)
T.nom = bandeau:CreateFontString(nil, "OVERLAY", "GameFontNormal")
T.nom:SetWidth(109)
T.nom:SetHeight(16)
T.nom:SetPoint("CENTER", f, "CENTER", 6, 202)
end
-- la croix du client, habillee en croix rouge de camelot
local function habillerCroix(croix, f)
croix:ClearAllPoints()
croix:SetPoint("TOPRIGHT", f, "TOPRIGHT", P.croixX, P.croixY)
croix:SetWidth(P.croix)
croix:SetHeight(P.croix)
croix:SetFrameLevel(f:GetFrameLevel() + 22)
for _, etat in ipairs({
{ "SetNormalTexture", "GetNormalTexture", "redbutton-exit" },
{ "SetPushedTexture", "GetPushedTexture", "redbutton-exit-pressed" },
{ "SetHighlightTexture", "GetHighlightTexture", "redbutton-highlight" },
}) do
local e = ForeverUI.AtlasEntry(etat[3])
croix[etat[1]](croix, e and e[1] or "")
local t = croix[etat[2]](croix)
if t then
ForeverUI.SetAtlas(t, etat[3], true)
t:ClearAllPoints()
t:SetAllPoints(croix)
if etat[3] == "redbutton-highlight" then t:SetBlendMode("ADD") end
end
end
end
-- l'argent : l'encadre et le bord dore (ThinGoldEdgeTemplate)
-- un repere : un cadre vide, pour la geometrie seulement
local function repere(f, nom)
local r = CreateFrame("Frame", nom, f)
r:EnableMouse(false)
return r
end
-- InsetFrameTemplate (le meme habillage que ForeverUI.DecorateInset), en
-- regions de la fenetre posees sur le repere
local MARBRE = "interface" .. SEP .. "ForeverUI" .. SEP .. "framegeneral" .. SEP .. "ui-background-marble"
local function encadrer(f, r)
local fond = f:CreateTexture(nil, "BORDER")
fond:SetTexture(MARBRE, true)
if fond.SetHorizTile then fond:SetHorizTile(true) fond:SetVertTile(true) end
fond:SetAllPoints(r)
r.fond = fond
local function coin(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 hg = coin("ui-frame-innertopleft", "TOPLEFT")
local hd = coin("ui-frame-innertopright", "TOPRIGHT")
local bg = coin("ui-frame-innerbotleftcorner", "BOTTOMLEFT", -1)
local bd = coin("ui-frame-innerbotright", "BOTTOMRIGHT", -1)
local function bord(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
bord("_ui-frame-innertoptile", "TOPLEFT", hg, "TOPRIGHT", "TOPRIGHT", hd, "TOPLEFT"):SetHeight(3)
bord("_ui-frame-innerbottile", "BOTTOMLEFT", bg, "BOTTOMRIGHT", "BOTTOMRIGHT", bd, "BOTTOMLEFT"):SetHeight(3)
bord("!ui-frame-innerlefttile", "TOPLEFT", hg, "BOTTOMLEFT", "BOTTOMLEFT", bg, "TOPLEFT"):SetWidth(3)
bord("!ui-frame-innerrighttile", "TOPRIGHT", hd, "BOTTOMRIGHT", "BOTTOMRIGHT", bd, "TOPRIGHT"):SetWidth(3)
end
local function habillerArgent(f)
local encadre = repere(f, "ForeverUITabardMoneyInset")
encadre:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 4, 4)
encadre:SetPoint("TOPRIGHT", f, "BOTTOMLEFT", 170, 25)
encadrer(f, encadre)
T.encadreArgent = encadre
local bord = repere(f, "ForeverUITabardMoneyBg")
bord:SetPoint("TOPRIGHT", f, "BOTTOMLEFT", 166, 24)
bord:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 7, 6)
bord.morceaux = {}
local function morceau(u1, u2, v1, v2)
local t = f:CreateTexture(nil, "ARTWORK")
table.insert(bord.morceaux, t)
t:SetTexture(P.argent)
t:SetTexCoord(u1, u2, v1, v2)
return t
end
local g = morceau(0.953125, 0.9921875, 0, 0.296875)
g:SetWidth(7)
g:SetPoint("TOPLEFT", bord, "TOPLEFT")
g:SetPoint("BOTTOMLEFT", bord, "BOTTOMLEFT")
local d = morceau(0, 0.0546875, 0, 0.296875)
d:SetWidth(7)
d:SetPoint("TOPRIGHT", bord, "TOPRIGHT")
d:SetPoint("BOTTOMRIGHT", bord, "BOTTOMRIGHT")
local m = morceau(0, 0.9921875, 0.3125, 0.609375)
m:SetPoint("TOPLEFT", g, "TOPRIGHT")
m:SetPoint("BOTTOMRIGHT", d, "BOTTOMLEFT")
T.bordArgent = bord
end
function T.habiller()
local f = TabardFrame
if not f or f.foreverHabille then return end
f.foreverHabille = true
f:SetWidth(P.largeur)
f:SetHeight(P.hauteur)
f:SetHitRectInsets(0, 0, 0, 0)
-- ce que camelot n'a plus : le cadre de WotLK et son grand fond
for _, r in ipairs({ f:GetRegions() }) do
if r.GetTexture and r:GetObjectType() == "Texture" then
local chemin = string.lower(tostring(r:GetTexture() or ""))
if string.find(chemin, "ui-character-general", 1, true) or string.find(chemin, "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
envelopper(f)
local encadre = repere(f, "ForeverUITabardInset")
encadre:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -60)
encadre:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -6, 26)
encadrer(f, encadre)
T.encadre = encadre
habillerArgent(f)
if TabardFrameCloseButton then habillerCroix(TabardFrameCloseButton, f) end
for _, p in ipairs(PLACES) 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(MODELE_LARGEUR) end
end
-- LE CADRAGE DU MODELE. Le rappel de SetUnit n'y change rien (verifie a
-- l'ecran le 2026-09-26) : le personnage reste en bas a gauche. Ce client
-- n'a que deux leviers sur un modele, SetPosition(profondeur, lateral,
-- hauteur) et SetModelScale, plus SetCamera. Le moteur les reprend apres
-- SetUnit : on les repose a chaque image pendant une seconde et demie apres
-- l'ouverture, comme pour la feuille de personnage. SetUnit ne remet pas la
-- position a zero ; /fui tabard pose et rend les valeurs. (La largeur du
-- modele, MODELE_LARGEUR, est posee avec les places, plus haut.)
-- LA CAMERA PAR DEFAUT (0) EST CELLE DU PORTRAIT : elle vise le visage et le
-- pose en bas a gauche. Le reglage du 26/09 la corrigeait par une position
-- en unites du monde (0,55 / 0,11 / 0,72), jugee sur un mort-vivant : avec un
-- gnome, dont la camera est bien plus proche, le personnage sortait par le
-- haut et on ne voyait que ses jambes (28/09). LA CAMERA 1 est celle du
-- personnage en pied : propre a chaque race, elle le cadre tout entier et au
-- centre du modele, quelle que soit sa taille (verifie a l'ecran sur le
-- gnome). Plus aucune position : pour le garder dans la moitie gauche de
-- l'encadre (valide le 26/09), c'est le cadre du modele qui prend cette
-- moitie -- de 4 a 154, jusqu'au panneau de personnalisation --, au bas et a
-- la hauteur de camelot.
local REGLAGE = { camera = 1, position = { 0, 0, 0 }, echelle = nil }
T.reglage = { camera = REGLAGE.camera, position = REGLAGE.position, echelle = REGLAGE.echelle }
local RATTRAPAGE = 1.5
local function appliquerReglage()
local m = TabardModel
if not m then return end
local r = T.reglage
if r.camera and m.SetCamera then m:SetCamera(r.camera) end
if r.echelle and m.SetModelScale then m:SetModelScale(r.echelle) end
if r.position and m.SetPosition then m:SetPosition(r.position[1], r.position[2], r.position[3]) end
end
T.appliquerReglage = appliquerReglage
local rattrapage = CreateFrame("Frame")
rattrapage:Hide()
rattrapage.reste = 0
rattrapage:SetScript("OnUpdate", function(self, ecoule)
appliquerReglage()
self.reste = self.reste - (ecoule or 0)
if self.reste <= 0 then self:Hide() end
end)
T.rattrapage = rattrapage
local function rattraper()
rattrapage.reste = RATTRAPAGE
rattrapage:Show()
end
-- /fui tabard ce que porte le modele
-- /fui tabard camera <n> SetCamera
-- /fui tabard echelle <s> SetModelScale
-- /fui tabard position <x> <y> <z> SetPosition(profondeur, lateral, hauteur)
-- /fui tabard defaut reprend le reglage retenu
-- /fui tabard client rend le modele au client (sans reglage)
function ForeverUI.TabardModelTune(argument)
local dire = function(texte) DEFAULT_CHAT_FRAME:AddMessage("|cff66ccffForeverUI|r " .. texte) end
local m = TabardModel
if not m then return end
local cle, reste = string.match(argument or "", "^(%S*)%s*(.*)$")
cle = string.lower(cle or "")
local r = T.reglage
if cle == "camera" then
r.camera = tonumber(reste)
elseif cle == "echelle" then
r.echelle = tonumber(reste)
elseif cle == "position" then
local x, y, z = string.match(reste, "^(%-?[%d%.]+)%s+(%-?[%d%.]+)%s+(%-?[%d%.]+)$")
if not x then
dire(L.TABARDFRAME_POSITION_USAGE)
return
end
r.position = { tonumber(x), tonumber(y), tonumber(z) }
elseif cle == "defaut" then
T.reglage = { camera = REGLAGE.camera, position = REGLAGE.position, echelle = REGLAGE.echelle }
m:SetUnit("player")
if m.InitializeTabardColors then m:InitializeTabardColors() end
elseif cle == "client" then
T.reglage = { camera = nil, position = { 0, 0, 0 }, echelle = 1 }
m:SetUnit("player")
if m.InitializeTabardColors then m:InitializeTabardColors() end
elseif cle ~= "" then
dire(L.TABARDFRAME_USAGE)
return
end
appliquerReglage()
rattraper()
local x, y, z
if m.GetPosition then x, y, z = m:GetPosition() end
dire(string.format(L.TABARDFRAME_STATE, tostring(T.reglage.camera),
tostring(m.GetModelScale and m:GetModelScale()), tostring(x), tostring(y), tostring(z)))
end
-- LE MODELE DECALE EN BAS A GAUCHE (constate le 2026-09-26, deja present
-- avec la fenetre de WotLK). A OPEN_TABARD_FRAME, le client 3.3.5 appelle
-- TabardModel:SetUnit("player") AVANT ShowUIPanel : le modele se cadre sur
-- une fenetre encore cachee. Camelot rappelle SetUnit quand la taille ou
-- l'affichage changent (TabardFrame_OnEvent : "This will happen even on
-- initial open"). On le rappelle donc une fois la fenetre affichee, a l'image
-- suivante, et on remet le tabard en cours comme le client le fait apres son
-- SetUnit (InitializeTabardColors, TabardFrame_UpdateTextures).
local recadrage = CreateFrame("Frame")
recadrage:Hide()
recadrage: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
rattraper()
end)
T.recadrage = recadrage
-- a l'ouverture : le portrait et le nom du marchand (TabardFrame_OnEvent les
-- vient de poser sur les regions du client), et le modele recadre
function T.ouvrir()
SetPortraitTexture(T.portrait, "npc")
T.nom:SetText(TabardFrameNameText and TabardFrameNameText:GetText() or UnitName("npc"))
recadrage:Show()
end
if TabardFrame then
T.habiller()
TabardFrame:HookScript("OnShow", T.ouvrir)
end