Files
moon-well-ui/data/addon/ForeverUI/CharacterFrame.lua
T
Adrestie 71618c8f63 mod-forever-ui: feuille du personnage, les six defauts du premier essai
QUATRE ONT LA MEME RACINE : les volets sont des cadres fils et recouvrent
toute region de leur parent, donc l'art du panneau pose sur le cadre
passait dessous. D'ou le contour qui ne collait pas aux fonds, le fond du
volet droit debordant sur le bord droit, celui du volet gauche mangeant
l'anneau, et le portrait vide. La source fait ce qu'il fallait faire : son
NineSlice est un cadre fils et son PortraitContainer un autre, a frameLevel
400. SetPanelArt accepte donc une option niveau, qui loge l'art dans un
cadre fils au-dessus.

Le coin haut gauche n'etait pas le bon : PortraitFrameBaseTemplate declare
layoutType = "PortraitFrameTemplate", qui ne differe de HeldBagLayout que
par lui -- un anneau plus large. SetPanelArt prend une option
coinHautGauche.

Le portrait etait vide parce que le balayage efface aussi celui du client.
On en pose un a nous, 44 centre sur le trou mesure de l'anneau (25 ; -22,5),
rempli par SetPortraitTexture. Le 44 vient du meme calcul que sur les sacs.

Le fond du volet droit ne remplissait pas : la source le declare SANS
ancrage, donc il remplit ; a sa taille d'atlas il laissait 81 px nus.

Le separateur s'etalait : common-framedivider porte un embout a chaque bout.
Il est decoupe en trois tranches par ses coordonnees de texture.

L'art d'epoque ne tient pas qu'au cadre : la feuille de 3.3.5 le repartit
sur ses sous-cadres, qui echappaient au balayage. La liste est balayee.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 02:51:11 +02:00

355 lines
13 KiB
Lua

-- ForeverUI : la feuille du personnage.
--
-- JALON 1 : le cadre, ses deux volets et les emplacements d'equipement.
-- Le volet droit (statistiques, onglets lateraux) et les autres onglets
-- -- reputation, competences, PvP, devises -- viendront ensuite.
--
-- RELEVE DES SOURCES -- camelot/, le seul dossier qui fasse foi ici avec
-- shared/ ; tout ce qui suit en vient.
--
-- camelot/CharacterFrameConstants.lua
-- CHARACTER_FRAME_WIDTH = 631, CHARACTER_FRAME_HEIGHT = 484
-- CHARACTER_FRAME_COLLAPSED_WIDTH = 398 (le volet droit se replie)
--
-- camelot/CharacterFrame.xml
-- CharacterFrame herite de PortraitFrameBaseTemplate : le meme panneau
-- que les sacs, avec portrait, titre et bouton de fermeture.
-- LeftPaneHost 398 de large, TOPLEFT (0, -20) et BOTTOMLEFT du cadre,
-- fond UI-Character-Info-General-BG a sa taille d'atlas
-- RightPaneHost 233 de large, accroche au TOPRIGHT du volet gauche,
-- fonds UI-Character-Info-Stat-BG puis -Stat-StoneBG en
-- ARTWORK, et un common-framedivider de 11 pose a -6
--
-- camelot/PaperDollFrame.xml
-- emplacement PaperDollItemSlotButtonTemplate, 40 x 40, son cadre est
-- l'atlas UI-Character-Info-GearSlot a sa taille (55)
-- colonne gauche TOPLEFT (24, -60) du volet gauche, puis chaque suivant
-- 6 px sous le precedent : tete, cou, epaules, dos, torse,
-- chemise, tabard, poignets
-- colonne droite TOPRIGHT (-20, -60), meme pas : mains, taille, jambes,
-- pieds, deux anneaux, deux bijoux
-- munitions 27 x 27, LEFT (+19) de l'emplacement de distance, son
-- cadre est UI-Character-Info-GearSlotSmall
-- modele la scene occupe tout le volet gauche
--
-- camelot/PaperDollFrame.lua, PaperDollItemSlotButton_OnLoad
-- l'arme principale se pose au BOTTOM du volet gauche : (-60, 30) quand
-- l'emplacement de distance est montre, (-40, 30) sinon -- et dans ce
-- dernier cas l'emplacement de distance est masque. Les deux autres
-- suivent, 6 px a droite chacun.
--
-- CE QUI DIFFERE, ET POURQUOI.
-- 3.3.5 montre toujours un emplacement de distance (arc, arme de jet ou
-- relique selon la classe) : on prend donc la variante a -60.
-- Le cadre du client fait 384 x 512 et ses emplacements 37 : tout est
-- redimensionne et repose, jamais recree -- ce sont des cadres du client
-- qui portent le glisser-deposer de l'equipement.
-- Les onglets du bas de 3.3.5 restent en place pour l'instant ; camelot
-- les met sur le cote (CharacterFrameModeTabs, 64 x 384), ce sera pour
-- le jalon qui traitera le volet droit.
local LARGEUR, HAUTEUR = 631, 484
local VOLET_GAUCHE, VOLET_DROIT = 398, 233
local COMBLE = 20 -- les volets commencent la, sous le titre
local EMPLACEMENT = 40
local ECART = 6
local GAUCHE_X, GAUCHE_Y = 24, -60
local DROITE_X, DROITE_Y = -20, -60
local ARME_X, ARME_Y = -60, 30 -- BOTTOM du volet gauche
local PETIT = 27 -- distance et munitions
local MUNITIONS_ECART = 19
local SEPARATEUR = 11 -- common-framedivider
local SEPARATEUR_EMBOUT = 4 -- ses deux embouts, mesures sur l'art
local NIVEAU_HABILLAGE = 5 -- l'art passe au-dessus des volets
local PORTRAIT = 44 -- voir le calcul plus bas
local PORTRAIT_X, PORTRAIT_Y = 25, -22.5
-- RELEVE -- PortraitFrameBaseTemplate : layoutType = "PortraitFrameTemplate".
-- Cette mise en page ne differe de celle des sacs QUE par son coin haut
-- gauche : un anneau plus large, pour un portrait de 62.
local COIN_PORTRAIT = "ui-frame-portraitmetal-cornertopleft"
-- Les cadres du client dont l'art d'epoque doit disparaitre : le cadre
-- lui-meme n'en porte qu'une partie.
local ANCIENS_CADRES = {
"CharacterFrame", "PaperDollFrame", "PetPaperDollFrame", "SkillFrame",
"ReputationFrame", "HonorFrame", "TokenFrame", "PaperDollItemsFrame",
"CharacterAttributesFrame", "CharacterResistanceFrame",
}
local ATLAS = {
fondGauche = "ui-character-info-general-bg",
fondDroit = "ui-character-info-stat-bg",
pierreDroite = "ui-character-info-stat-stonebg",
separateur = "common-framedivider",
emplacement = "ui-character-info-gearslot",
petitEmplacement = "ui-character-info-gearslotsmall",
}
local COLONNE_GAUCHE = {
"Head", "Neck", "Shoulder", "Back", "Chest", "Shirt", "Tabard", "Wrist",
}
local COLONNE_DROITE = {
"Hands", "Waist", "Legs", "Feet", "Finger0", "Finger1", "Trinket0", "Trinket1",
}
local RANGEE_ARMES = { "MainHand", "SecondaryHand", "Ranged" }
local voletGauche, voletDroit
-- L'art d'epoque ne tient pas qu'au cadre : la feuille de 3.3.5 le repartit
-- sur ses sous-cadres, qui sont des cadres fils et echappent donc a un
-- balayage du seul CharacterFrame. On balaie la liste.
--
-- Le portrait du client y passe aussi -- c'est une region du cadre. On en
-- pose un a nous, plus bas, sinon l'anneau reste vide.
local function effacerArtDepoque()
for _, nom in ipairs(ANCIENS_CADRES) do
local cadre = _G[nom]
if cadre and cadre.GetRegions then
local regions = { cadre:GetRegions() }
for _, region in ipairs(regions) do
if region and region.GetObjectType
and region:GetObjectType() == "Texture" then
region:SetAlpha(0)
end
end
end
end
end
local function monterVolets(cadre)
if voletGauche then
return
end
-- LeftPaneHost : 398 de large, du haut moins le comble jusqu'au bas.
voletGauche = CreateFrame("Frame", "ForeverUICharacterLeftPane", cadre)
voletGauche:SetWidth(VOLET_GAUCHE)
voletGauche:SetPoint("TOPLEFT", cadre, "TOPLEFT", 0, -COMBLE)
voletGauche:SetPoint("BOTTOMLEFT", cadre, "BOTTOMLEFT", 0, 0)
local fondGauche = voletGauche:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(fondGauche, ATLAS.fondGauche)
fondGauche:SetPoint("TOPLEFT", voletGauche, "TOPLEFT", 0, 0)
-- RightPaneHost : 233, accroche a la droite du volet gauche.
voletDroit = CreateFrame("Frame", "ForeverUICharacterRightPane", cadre)
voletDroit:SetWidth(VOLET_DROIT)
voletDroit:SetPoint("TOPLEFT", voletGauche, "TOPRIGHT", 0, 0)
voletDroit:SetPoint("BOTTOMLEFT", voletGauche, "BOTTOMRIGHT", 0, 0)
-- La source declare ce fond SANS ancrage : il remplit son volet. Pose a
-- sa taille d'atlas (233 x 383) il laisserait 81 px nus en bas, le volet
-- en faisant 464.
local fondDroit = voletDroit:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(fondDroit, ATLAS.fondDroit, true)
fondDroit:SetAllPoints(voletDroit)
local pierre = voletDroit:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(pierre, ATLAS.pierreDroite)
pierre:SetPoint("TOPLEFT", voletDroit, "TOPLEFT", 0, 0)
voletDroit.pierre = pierre
-- Le separateur des deux volets. Son element porte un embout a chaque
-- bout : tendu tel quel sur 464 px, ils s'etalent. Trois tranches.
local separateur = ForeverUI.CreateVerticalDivider(voletDroit, ATLAS.separateur,
SEPARATEUR_EMBOUT, voletDroit:GetFrameLevel() + 1)
separateur:SetWidth(SEPARATEUR)
separateur:SetPoint("TOPLEFT", voletDroit, "TOPLEFT", -6, -1)
separateur:SetPoint("BOTTOMLEFT", voletDroit, "BOTTOMLEFT", -6, 0)
voletDroit.separateur = separateur
ForeverUI.CharacterPanes = { gauche = voletGauche, droit = voletDroit }
end
-- LE PORTRAIT. La source le pose en 62 x 62 dans PortraitContainer, un
-- cadre fils de niveau 400, et l'arrondit par un masque. Ici c'est le trou
-- de l'anneau qui decoupe, comme sur les sacs.
--
-- L'anneau de ce coin, MESURE au pixel a la taille ou il est dessine :
-- transparent jusqu'a 13 du centre, degrade jusqu'a 23, metal opaque de 24
-- a 31. Aucune taille ne couvre le degrade (il faudrait 46) tout en cachant
-- ses coins (il faudrait 43,8) : on prend 44, dont les coins tombent a 31,1
-- -- juste sous le metal -- et dont les bords s'arretent dans le degrade,
-- ou l'anneau est de toute facon a demi transparent. Le trou mesure place
-- son centre a (25 ; -22,5) du coin du cadre, la ou la source met le sien
-- a (26 ; -24).
local function poserPortrait(cadre)
local hote = cadre.foreverHabillage or cadre
if not cadre.foreverPortrait then
local portrait = hote:CreateTexture(nil, "BACKGROUND")
portrait:SetWidth(PORTRAIT)
portrait:SetHeight(PORTRAIT)
portrait:SetPoint("CENTER", hote, "TOPLEFT", PORTRAIT_X, PORTRAIT_Y)
cadre.foreverPortrait = portrait
end
if SetPortraitTexture then
SetPortraitTexture(cadre.foreverPortrait, "player")
end
end
-- RELEVE -- PaperDollItemSlotButtonTemplate : 40 x 40, et son cadre est
-- l'atlas UI-Character-Info-GearSlot pose a sa taille, centre.
local function habillerEmplacement(nom, cote, atlasCadre)
local bouton = _G["Character" .. nom .. "Slot"]
if not bouton then
return nil
end
bouton:SetWidth(cote)
bouton:SetHeight(cote)
if not bouton.foreverCadre then
-- L'art d'epoque du bouton s'efface ; le notre se pose dessous,
-- comme la source qui met le sien en BACKGROUND d'un cadre fils.
local normale = bouton:GetNormalTexture()
if normale then
normale:SetAlpha(0)
end
local cadre = bouton:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(cadre, atlasCadre)
cadre:SetPoint("CENTER", bouton, "CENTER", 0, 0)
bouton.foreverCadre = cadre
end
local icone = _G["Character" .. nom .. "SlotIconTexture"]
if icone then
icone:ClearAllPoints()
icone:SetAllPoints(bouton)
icone:SetTexCoord(0, 1, 0, 1)
end
return bouton
end
local function poserEmplacements()
if InCombatLockdown() then
return
end
local precedent
for index, nom in ipairs(COLONNE_GAUCHE) do
local bouton = habillerEmplacement(nom, EMPLACEMENT, ATLAS.emplacement)
if bouton then
bouton:ClearAllPoints()
if index == 1 then
bouton:SetPoint("TOPLEFT", voletGauche, "TOPLEFT", GAUCHE_X, GAUCHE_Y)
else
bouton:SetPoint("TOPLEFT", precedent, "BOTTOMLEFT", 0, -ECART)
end
precedent = bouton
end
end
precedent = nil
for index, nom in ipairs(COLONNE_DROITE) do
local bouton = habillerEmplacement(nom, EMPLACEMENT, ATLAS.emplacement)
if bouton then
bouton:ClearAllPoints()
if index == 1 then
bouton:SetPoint("TOPRIGHT", voletGauche, "TOPRIGHT", DROITE_X, DROITE_Y)
else
bouton:SetPoint("TOPLEFT", precedent, "BOTTOMLEFT", 0, -ECART)
end
precedent = bouton
end
end
-- La rangee des armes, posee sur le bas du volet gauche. 3.3.5 montre
-- toujours un emplacement de distance : on prend la variante a -60.
precedent = nil
for index, nom in ipairs(RANGEE_ARMES) do
local cote = (nom == "Ranged") and PETIT or EMPLACEMENT
local atlasCadre = (nom == "Ranged") and ATLAS.petitEmplacement or ATLAS.emplacement
local bouton = habillerEmplacement(nom, cote, atlasCadre)
if bouton then
bouton:ClearAllPoints()
if index == 1 then
bouton:SetPoint("BOTTOM", voletGauche, "BOTTOM", ARME_X, ARME_Y)
else
bouton:SetPoint("TOPLEFT", precedent, "TOPRIGHT", ECART, 0)
end
precedent = bouton
end
end
local munitions = habillerEmplacement("Ammo", PETIT, ATLAS.petitEmplacement)
if munitions and precedent then
munitions:ClearAllPoints()
munitions:SetPoint("LEFT", precedent, "RIGHT", MUNITIONS_ECART, 0)
end
end
-- La scene de camelot occupe tout le volet gauche ; le modele de 3.3.5
-- prend la meme place.
local function poserModele()
local modele = CharacterModelFrame
if not modele or InCombatLockdown() then
return
end
modele:ClearAllPoints()
modele:SetPoint("TOPLEFT", voletGauche, "TOPLEFT", 0, 0)
modele:SetPoint("BOTTOMRIGHT", voletGauche, "BOTTOMRIGHT", 0, 0)
end
local function habiller()
local cadre = CharacterFrame
if not cadre or InCombatLockdown() then
return
end
cadre:SetWidth(LARGEUR)
cadre:SetHeight(HAUTEUR)
if not cadre.foreverSkinned then
-- L'ORDRE COMPTE. Les volets sont des cadres fils : ils se dessinent
-- au-dessus de toute region de leur parent. L'art du panneau doit
-- donc vivre dans un cadre fils de niveau superieur, comme le
-- NineSlice de la source -- sinon les fonds des volets recouvrent le
-- metal, debordent sur le bord droit et mangent l'anneau du portrait.
effacerArtDepoque()
monterVolets(cadre)
ForeverUI.SetPanelArt(cadre, {
coinHautGauche = COIN_PORTRAIT,
niveau = NIVEAU_HABILLAGE,
})
cadre.foreverSkinned = true
end
effacerArtDepoque()
poserPortrait(cadre)
if ForeverUI.UpdatePanelCorners then
ForeverUI.UpdatePanelCorners(cadre)
end
poserModele()
poserEmplacements()
end
ForeverUI.CharacterSheet = { Apply = habiller }
if hooksecurefunc then
for _, nomFonction in ipairs({ "CharacterFrame_ShowSubFrame", "PaperDollFrame_OnShow",
"PaperDollFrame_SetLevel" }) do
if type(_G[nomFonction]) == "function" then
hooksecurefunc(nomFonction, habiller)
end
end
end
local veilleur = CreateFrame("Frame", "ForeverUICharacterWatcher")
veilleur:RegisterEvent("PLAYER_ENTERING_WORLD")
veilleur:RegisterEvent("PLAYER_REGEN_ENABLED")
veilleur:RegisterEvent("UNIT_INVENTORY_CHANGED")
veilleur:SetScript("OnEvent", habiller)
if CharacterFrame then
CharacterFrame:HookScript("OnShow", habiller)
end
habiller()