Files
moon-well-ui/data/addon/ForeverUI/Titles.lua
T
Adrestie 8599265c5d Feuille : un troisieme onglet au volet droit, les titres
A la demande : camelot n a pas cet onglet -- son PAPERDOLL_SIDEBARS vaut
{STATS, EQUIPMENTMANAGER, PET} -- et l icone vient de mainline. La
disposition a trois, elle, vient de camelot : c est le DEUXIEME onglet
qui porte l ancre, le troisieme colle a sa droite et le premier a sa
gauche.

La liste prend toute la largeur du volet moins la marge des
statistiques, et le meme fond alterne qu elles. Le menu deroulant de
titre du client se tait a chaque passage : PlayerTitleFrame_UpdateTitles
le remontre des qu un titre change.

Le nom de la feuille suit le titre a UNIT_NAME_UPDATE, et non au clic :
SetCurrentTitle part au serveur, et UnitPVPName ne rend le nouveau nom
qu a la reponse.

Le TitleManagerPane de camelot est reste sur une ancre morte, sous la
bande de pierre : on prend celle de l EquipmentManagerPane.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 13:10:24 +02:00

407 lines
14 KiB
Lua

-- ForeverUI : la liste des titres, page du volet droit.
--
-- RELEVE -- camelot/paperdollframe.xml et paperdollframe.lua, lus en entier,
-- plus le PaperDollFrame.xml et le PaperDollFrame.lua du client 3.3.5, lus
-- dans l'archive.
--
-- PlayerTitleButtonTemplate, chez camelot 169 x 22
-- BgTop / BgBottom Char-Stat-Top, les deux BOUTS DE LA LISTE : montres
-- seulement sur la premiere et la derniere ligne
-- BgMiddle Char-Stat-Middle, 169 x 8, LEFT x = 1, tuilee
-- verticalement, rognee a 0,00390625 .. 0,66406250
-- Stripe TOPLEFT (1, 0) a BOTTOMRIGHT (0, 0), la couleur
-- STRIPE_COLOR = 0,9 / 0,9 / 1 a 0,1 d'alpha, UNE
-- LIGNE SUR DEUX
-- Check Interface/Buttons/UI-CheckBox-Check, 16 x 16,
-- LEFT (8, 0), masquee tant que la ligne n'est pas
-- choisie
-- SelectedBar UI-FriendsFrame-HighlightBar, alpha 0,4, ADD
-- text GameFontNormalSmallLeft, LEFT du RIGHT de la coche
-- (3, 0), RIGHT (-3, 0)
-- Highlight UI-FriendsFrame-HighlightBar-Blue, ADD
--
-- LES DONNEES. GetNumTitles, IsTitleKnown, GetTitleName, GetCurrentTitle et
-- SetCurrentTitle : les cinq sont dans le binaire de 3.3.5, verifie, et le
-- client s'en sert lui-meme dans PlayerTitleFrame_UpdateTitles.
--
-- IsTitleKnown REND UN NOMBRE, 0 OU 1. Zero est VRAI en Lua : le client
-- ecrit `if ( IsTitleKnown(i) ~= 0 )`, ligne 2605, et c'est la seule
-- ecriture juste.
-- GetTitleName rend le nom avec ses espaces -- "Private " -- d'ou le
-- strtrim que le client applique lui aussi.
-- GetCurrentTitle rend 0 quand rien n'est choisi, -1 pour "Aucun", sinon
-- l'identifiant. "Aucun" se pose par SetCurrentTitle(-1).
--
-- CE QUI DIFFERE, ET POURQUOI.
--
-- * PAS DE BOUTS DE LISTE. Char-Stat-Top est un MODELE de texture, pas un
-- fichier : il n'est ni dans camelot -- ou aucun XML ne le definit --
-- ni dans le FrameXML de 3.3.5, ni sous ce nom dans le listfile. Seul
-- Char-Stat-Middle existe. La liste n'a donc pas ses deux bouts
-- arrondis ; c'est un manque, signale, pas un choix.
-- * LA BANDE EST ETIREE, PAS TUILEE. camelot tuile une bande de 8 px sur
-- la hauteur de la ligne. Marier SetTexCoord et SetVertTile n'est pas
-- sur dans ce client ; et la bande ne varie verticalement que de deux a
-- cinq niveaux de luminance -- releve texel par texel -- ce que l'oeil
-- ne voit pas. On l'etire.
-- * L'ONGLET N'EST PAS DANS camelot. Son PAPERDOLL_SIDEBARS vaut
-- {STATS, EQUIPMENTMANAGER, PET} : pas de titres. L'onglet vient de
-- mainline/PaperDollFrameConstants.lua, PAPERDOLL_SIDEBARTAB_TITLES, et
-- il est ici A LA DEMANDE.
-- * LE PANNEAU N'EST PAS POSE OU camelot le pose. Son TitleManagerPane
-- s'ancre au TOPLEFT du volet droit (4, -4), donc SOUS la bande de
-- pierre et les onglets, qui le recouvriraient : l'ancre est restee
-- telle quelle le jour ou les titres ont quitte PAPERDOLL_SIDEBARS. On
-- prend celle de l'EquipmentManagerPane, la seule vivante de ce volet :
-- du bas de la pierre au bas du volet.
-- * LA LIGNE PREND TOUTE LA LARGEUR DU VOLET, moins la marge des
-- statistiques -- 20 de chaque cote, STAT_MARGE. camelot lui donne 169
-- sur un volet de 233 ; A LA DEMANDE, les deux pages du volet ont
-- desormais le meme bord. La bande s'etire avec la ligne.
-- * PAS DE BARRE DE DEFILEMENT, comme la reputation et les competences :
-- la molette suffit d'ici la.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
-- LA LIGNE PREND TOUTE LA LARGEUR DU VOLET, MOINS SA MARGE.
--
-- A LA DEMANDE, et non d'apres la source : camelot donne 169 de large a son
-- PlayerTitleButtonTemplate, sur un volet qui en fait 233. Ici la liste
-- s'aligne sur les lignes de statistiques du meme volet -- STAT_MARGE, 20 de
-- chaque cote -- pour que les deux pages du volet aient le meme bord.
--
-- LA BANDE S'ETIRE AVEC ELLE. Char-Stat-Middle porte 169 px de matiere --
-- releve : x 0 a 170, transparent au-dela -- soit une barre finie avec ses
-- deux bords. Etiree a 193, ses bords passent de 1 a 1,14 px : l'oeil ne le
-- voit pas, et un decoupage en trois tranches pour si peu ne se justifie pas.
local LIGNE_H = 22
local MARGE = 20 -- STAT_MARGE, la marge des statistiques
local LISTE_Y = -4 -- SetPadding(4, 0, 2, 0, 0)
local COCHE = 16
local COCHE_X = 8
local TEXTE_X, TEXTE_X2 = 3, -3
local CHOISI_ALPHA = 0.4
-- LE FOND ALTERNE, LE MEME QUE LES LIGNES DE STATISTIQUES.
--
-- A LA DEMANDE. Les deux bandes de paperdollinfopart1c60, les deux seules
-- bandes horizontales de la planche dont l'alpha s'eteint aux deux bouts :
-- UI-Character-Info-ItemLevel-Bounce 204 x 21, SOMBRE (27, 21, 16) a 255
-- UI-Character-Info-Line-Bounce 213 x 18, CLAIRE (87, 67, 46) a 102
-- La sombre en premier, comme dans les statistiques.
--
-- L'ALTERNANCE SUIT L'ENTREE, PAS LA PLACE A L'ECRAN : c'est l'index dans
-- la liste, celui qui tient compte du defilement. camelot raye de meme --
-- PaperDollTitlesPane_InitButton, sur `index % 2`.
local ATLAS_FOND_SOMBRE = "ui-character-info-itemlevel-bounce"
local ATLAS_FOND_CLAIR = "ui-character-info-line-bounce"
local CHEMIN_COCHE = "Interface\\Buttons\\UI-CheckBox-Check"
local CHEMIN_CHOISI = "Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar"
local CHEMIN_SURVOL =
"Interface\\ForeverUI\\FriendsFrame\\UI-FriendsFrame-HighlightBar-Blue"
-- LA TAILLE DU VOLET, PAR CONSTRUCTION.
--
-- Un contenu se batit a sa PREMIERE OUVERTURE, qui peut preceder la pose de
-- la fenetre : GetHeight rendrait alors zero, et la liste ne poserait qu'une
-- ligne. Le meme garde-fou que la reputation et les competences.
-- volet droit 233 x 464, et la bande de pierre en prend 85 par le haut.
local VOLET_L, VOLET_H, PIERRE_H = 233, 464, 85
local panneau, decalage, lignes, titres, choisi = nil, 0, {}, {}, -1
-- -------------------------------------------------------------- les donnees
local function nomAucun()
return PLAYER_TITLE_NONE or NONE or "None"
end
-- LES TITRES QUE LE PERSONNAGE POSSEDE.
--
-- camelot reserve la premiere place a "Aucun" avec un nom fait d'espaces,
-- trie, puis le renomme : l'espace passant avant les lettres, "Aucun" reste
-- en tete quel que soit son intitule. On obtient le meme resultat en triant
-- les titres connus puis en posant "Aucun" devant, et cela se lit mieux.
local function lireTitres()
local liste = {}
local nombre = GetNumTitles and GetNumTitles() or 0
for identifiant = 1, nombre do
if IsTitleKnown and IsTitleKnown(identifiant) ~= 0 then
local nom = GetTitleName and GetTitleName(identifiant)
if nom then
nom = strtrim(nom)
if nom ~= "" then
liste[#liste + 1] = { id = identifiant, nom = nom }
end
end
end
end
table.sort(liste, function(a, b) return a.nom < b.nom end)
table.insert(liste, 1, { id = -1, nom = nomAucun() })
return liste
end
-- LE TITRE PORTE, tel que camelot le retient : l'identifiant courant s'il
-- est un titre connu, "Aucun" sinon -- GetCurrentTitle rend 0 tant que rien
-- n'a ete choisi.
local function titrePorte()
local courant = GetCurrentTitle and GetCurrentTitle() or 0
local nombre = GetNumTitles and GetNumTitles() or 0
if courant and courant > 0 and courant <= nombre
and IsTitleKnown and IsTitleKnown(courant) ~= 0 then
return courant
end
return -1
end
-- --------------------------------------------------------------- l'affichage
local function poserChoix(ligne, marque)
if marque then
ligne.coche:Show()
ligne.choisi:Show()
else
ligne.coche:Hide()
ligne.choisi:Hide()
end
end
-- LA LARGEUR D'UNE LIGNE : celle du volet, sa marge deduite. Le meme
-- garde-fou que la hauteur -- un contenu peut se batir avant la pose de la
-- fenetre, et GetWidth rendrait alors zero.
local function largeurLigne()
local largeur = (panneau and panneau:GetWidth()) or 0
if largeur < 50 then
largeur = VOLET_L
end
return largeur - 2 * MARGE
end
local function creerLigne(rang)
local ligne = CreateFrame("Button", "ForeverUITitleRow" .. rang, panneau)
ligne:SetWidth(largeurLigne())
ligne:SetHeight(LIGNE_H)
-- LE FOND : une seule texture, dont l'atlas change d'un passage a
-- l'autre. Le troisieme argument dit de NE PAS toucher a la taille --
-- elle vient des deux ancres.
local fond = ligne:CreateTexture(nil, "BACKGROUND")
fond:SetPoint("TOPLEFT", ligne, "TOPLEFT", 0, 0)
fond:SetPoint("BOTTOMRIGHT", ligne, "BOTTOMRIGHT", 0, 0)
ligne.fond = fond
local choisiBarre = ligne:CreateTexture(nil, "OVERLAY")
choisiBarre:SetTexture(CHEMIN_CHOISI)
choisiBarre:SetBlendMode("ADD")
choisiBarre:SetAlpha(CHOISI_ALPHA)
choisiBarre:SetAllPoints(ligne)
choisiBarre:Hide()
ligne.choisi = choisiBarre
local coche = ligne:CreateTexture(nil, "BORDER")
coche:SetTexture(CHEMIN_COCHE)
coche:SetWidth(COCHE)
coche:SetHeight(COCHE)
coche:SetPoint("LEFT", ligne, "LEFT", COCHE_X, 0)
coche:Hide()
ligne.coche = coche
local texte = ligne:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
texte:SetJustifyH("LEFT")
texte:SetPoint("LEFT", coche, "RIGHT", TEXTE_X, 0)
texte:SetPoint("RIGHT", ligne, "RIGHT", TEXTE_X2, 0)
ligne.texte = texte
ligne:SetFontString(texte)
-- PIEGE 3.3.5 : SetHighlightTexture prend un CHEMIN et un mode de
-- fondu, jamais un objet texture. On ne peut donc pas la fabriquer a
-- part, comme le XML de camelot le fait.
ligne:SetHighlightTexture(CHEMIN_SURVOL, "ADD")
local survol = ligne.GetHighlightTexture and ligne:GetHighlightTexture()
if survol then
survol:SetAllPoints(ligne)
end
ligne:SetScript("OnClick", function(self)
if not self.titreId then
return
end
if PlaySound then
PlaySound("igMainMenuOptionCheckBoxOff")
end
if SetCurrentTitle then
SetCurrentTitle(self.titreId)
end
choisi = self.titreId
if ForeverUI.TitlesUpdate then
ForeverUI.TitlesUpdate()
end
end)
if rang == 1 then
ligne:SetPoint("TOPLEFT", panneau, "TOPLEFT", MARGE, LISTE_Y)
else
ligne:SetPoint("TOPLEFT", lignes[rang - 1], "BOTTOMLEFT", 0, 0)
end
lignes[rang] = ligne
return ligne
end
-- COMBIEN DE LIGNES TIENNENT dans le panneau, sa marge du haut deduite.
local function tiennent()
local hauteur = (panneau and panneau:GetHeight()) or 0
if hauteur < 50 then
hauteur = VOLET_H - PIERRE_H
end
local compte = math.floor((hauteur + LISTE_Y) / LIGNE_H)
if compte < 1 then
compte = 1
end
return compte
end
-- UN PASSAGE : empiler les lignes depuis le decalage, jusqu'au bas du
-- panneau.
local function poser()
if not panneau then
return 0
end
local places = tiennent()
local total = #titres
if decalage > 0 and decalage + places > total then
decalage = math.max(0, total - places)
end
local largeur = largeurLigne()
local posees = 0
for rang = 1, places do
local index = decalage + rang
local titre = titres[index]
local ligne = lignes[rang] or creerLigne(rang)
ligne:SetWidth(largeur)
if titre then
ligne.titreId = titre.id
ligne.texte:SetText(titre.nom)
poserChoix(ligne, titre.id == choisi)
ForeverUI.SetAtlas(ligne.fond,
(index % 2 == 1) and ATLAS_FOND_SOMBRE or ATLAS_FOND_CLAIR,
true)
ligne:Show()
posees = posees + 1
else
ligne.titreId = nil
ligne:Hide()
end
end
for rang = places + 1, #lignes do
lignes[rang]:Hide()
end
return posees
end
-- LE MENU DEROULANT DU CLIENT S'EFFACE, ET A CHAQUE PASSAGE.
--
-- PlayerTitleFrame_UpdateTitles le RAPPELLE -- PlayerTitleFrame:Show(),
-- ligne 2624 -- des qu'un titre change. Le masquer une fois ne suffirait
-- donc pas.
local MENU_DU_CLIENT = { "PlayerTitleFrame", "PlayerTitlePickerFrame" }
local function etoufferMenuDuClient()
for _, nom in ipairs(MENU_DU_CLIENT) do
local cadre = _G[nom]
if cadre then
cadre:Hide()
if cadre.EnableMouse then
cadre:EnableMouse(false)
end
end
end
end
local function maj()
etoufferMenuDuClient()
if not panneau then
return
end
titres = lireTitres()
choisi = titrePorte()
poser()
end
ForeverUI.TitlesUpdate = maj
-- ----------------------------------------------------------- la construction
local function monter(hote)
if panneau then
return panneau, {}
end
-- L'ancre de l'EquipmentManagerPane : du bas de la pierre au bas du
-- volet. Voir l'entete pour le TitleManagerPane de camelot, reste sur
-- une ancre morte.
panneau = CreateFrame("Frame", "ForeverUITitlesPane", hote)
if hote.pierre then
panneau:SetPoint("TOPLEFT", hote.pierre, "BOTTOMLEFT", 0, 0)
else
panneau:SetPoint("TOPLEFT", hote, "TOPLEFT", 0, 0)
end
panneau:SetPoint("BOTTOMRIGHT", hote, "BOTTOMRIGHT", 0, 0)
panneau:EnableMouseWheel(true)
panneau:SetScript("OnMouseWheel", function(_self, sens)
decalage = math.max(0, math.min(decalage - sens, #titres - tiennent()))
poser()
end)
maj()
return panneau, {}
end
ForeverUI.TitlesPane = { Build = monter, Update = maj }
-- Le client refait sa liste dans PlayerTitleFrame_UpdateTitles : on passe
-- apres, pour reprendre la notre et taire la sienne.
if hooksecurefunc and type(_G["PlayerTitleFrame_UpdateTitles"]) == "function" then
hooksecurefunc("PlayerTitleFrame_UpdateTitles", function()
maj()
end)
end
-- CE QUI FAIT BOUGER LA LISTE. KNOWN_TITLES_UPDATE annonce un titre gagne ;
-- UNIT_NAME_UPDATE sur le joueur, le titre porte qui change. camelot ecoute
-- exactement ces deux evenements.
local veilleur = CreateFrame("Frame")
veilleur:RegisterEvent("KNOWN_TITLES_UPDATE")
veilleur:RegisterEvent("UNIT_NAME_UPDATE")
veilleur:SetScript("OnEvent", function(_self, evenement, unite)
if evenement == "UNIT_NAME_UPDATE" and unite ~= "player" then
return
end
maj()
end)
-- TEMOIN -- /fui titres.
function ForeverUI.TitlesDebug()
local dire = function(t)
DEFAULT_CHAT_FRAME:AddMessage("|cff66ccffForeverUI|r " .. t)
end
local liste = lireTitres()
dire(string.format("titres : %d connu(s) + Aucun, porte=%s, decalage=%d,"
.. " %d ligne(s) posables", #liste - 1, tostring(titrePorte()),
decalage, panneau and tiennent() or 0))
local noms = {}
for index = 1, math.min(#liste, 8) do
noms[#noms + 1] = string.format("%s(%d)", liste[index].nom,
liste[index].id)
end
dire(" " .. table.concat(noms, ", "))
for _, nom in ipairs(MENU_DU_CLIENT) do
local cadre = _G[nom]
dire(string.format(" %s : %s", nom,
cadre and (cadre:IsShown() and "VISIBLE" or "masque") or "absent"))
end
end