Files
moon-well-ui/data/addon/ForeverUI/Panes.lua
T
Adrestie e3b4e3360d Feuille : le volet droit reste ouvert d'un onglet a l'autre
La source le dit : UpdateRightPaneHeader ne masque que le StoneBg -- "the
stone header backs the PaperDoll sidebar tabs, so it should not render on
tabs that have none" -- et RightPaneHost demeure. Chaque onglet aura ses
propres informations a y mettre ; le volet leur est reserve des maintenant,
vide en attendant.

Ce qui s'en va d'un onglet a l'autre n'est donc pas le volet mais le
MOBILIER du groupe : bande de pierre, onglets de page et ligne de niveau
pour le personnage -- les trois choses exactes que la source masque.

Le mobilier est un troisieme niveau dans Panes.lua, parce que deux ne
suffisaient pas : la pierre et les onglets de page appartiennent au groupe
et non a l'une de ses deux pages, ou passer de l'une a l'autre les aurait
emportes.

Le repli, lui, traverse les onglets : replie sur le personnage, il l'est
encore sur les competences, et se defait depuis n'importe lequel.

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

294 lines
9.0 KiB
Lua

-- ForeverUI : les hotes et leurs contenus.
--
-- CE QUE C'EST, ET POURQUOI.
--
-- Une fenetre moderne n'est pas un bloc : c'est un ou deux HOTES -- des
-- surfaces fixes -- dans lesquels des CONTENUS se remplacent. camelot le dit
-- ainsi dans CharacterFrame.xml : LeftPaneHost et RightPaneHost, et une
-- SidePane par onglet. Changer d'onglet n'y recalcule rien : un contenu se
-- masque, un autre se montre.
--
-- LE DEFAUT QUE CELA CORRIGE. La feuille de personnage decidait sa visibilite
-- a cinq endroits -- l'habillage, les deux onglets du volet, le repli, et la
-- fonction des statistiques -- et reposait TOUTE sa geometrie a chaque
-- evenement. D'ou deux fautes que rien ne pouvait prevenir :
-- * le panneau du gestionnaire d'equipement restait a l'ecran quand on
-- changeait d'onglet lateral, parce qu'il appartient a NOTRE volet et
-- non au PaperDollFrame du client, que lui seul masque ;
-- * les barres de reputation traversaient le volet droit, parce que le
-- cadre du client garde la taille de la fenetre d'origine et que
-- personne ne le bornait.
-- Tant que la visibilite se decide partout, chaque nouvel ecran rouvre les
-- memes plaies. Elle se decide donc ICI, et nulle part ailleurs.
--
-- LE MODELE, en trois mots :
--
-- HOTE une surface qui accueille : un volet, une colonne.
-- GROUPE ce qu'un onglet ouvre. Un groupe touche TOUS les hotes a la
-- fois : c'est un ecran entier.
-- PAGE un contenu dans un hote pour un groupe donne. Plusieurs pages
-- dans le meme hote et le meme groupe se remplacent entre elles
-- -- ce sont des boutons, pas des onglets.
-- MOBILIER ce qu'un hote porte pour un groupe QUELLE QUE SOIT la page : la
-- bande de pierre, les onglets qui choisissent la page. camelot le
-- traite ainsi -- UpdateRightPaneHeader ne masque que le StoneBg,
-- l'hote, lui, reste en place.
--
-- Un contenu se CONSTRUIT une seule fois, a sa premiere ouverture, et pose sa
-- geometrie a ce moment-la. Ensuite il ne fait plus que paraitre et
-- disparaitre. Rien n'est recalcule.
--
-- CE QU'UN CONTENU DECLARE POSSEDER. Notre racine est un cadre a nous : ses
-- fils la suivent. Mais un ecran de 3.3.5 est fait de cadres DU CLIENT, que
-- l'on ne reparente pas -- leur niveau et leur strate s'y perdraient, et des
-- reglages deja valides avec. Un contenu declare donc aussi la LISTE des
-- cadres du client qu'il possede, et la bibliotheque les montre et les masque
-- avec sa racine. Une seule boucle, un seul endroit.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local Panes = {}
ForeverUI.Panes = Panes
local hotes = {} -- nom -> { cadre, contenus, groupe, page, montre }
local ordreHotes = {}
-- --------------------------------------------------------------- les hotes
-- Declarer un hote. Le cadre existe deja : la bibliotheque ne le cree pas,
-- elle ne fait que savoir ou les contenus vont.
function Panes.NewHost(nom, cadre)
local hote = hotes[nom]
if not hote then
hote = { nom = nom, contenus = {}, ordre = {}, pages = {},
mobilier = {}, montre = true }
hotes[nom] = hote
ordreHotes[#ordreHotes + 1] = nom
end
hote.cadre = cadre
return hote
end
function Panes.Host(nom)
local hote = hotes[nom]
return hote and hote.cadre
end
-- ---------------------------------------------------------- les contenus
-- def = { hote, groupe, id, construire }
--
-- construire(hoteCadre) est appelee UNE fois, a la premiere ouverture. Elle
-- rend, dans l'ordre : la racine -- un cadre a nous, ou nil -- et la liste
-- des cadres du client que ce contenu possede.
function Panes.Register(def)
local hote = hotes[def.hote]
if not hote then
error("ForeverUI.Panes : hote inconnu -- " .. tostring(def.hote))
end
local contenu = {
id = def.id,
groupe = def.groupe,
construire = def.construire,
hote = hote,
bati = false,
cadres = {},
}
hote.contenus[def.id] = contenu
hote.ordre[#hote.ordre + 1] = def.id
-- La premiere page declaree pour un groupe en devient la page par defaut.
if hote.pages[def.groupe] == nil then
hote.pages[def.groupe] = def.id
end
return contenu
end
local function batir(contenu)
if contenu.bati then
return
end
contenu.bati = true -- avant l'appel : pas de boucle
if contenu.construire then
local racine, cadres = contenu.construire(contenu.hote.cadre)
contenu.racine = racine
contenu.cadres = cadres or {}
end
end
-- Un contenu possede aussi des cadres du client, declares apres coup : un
-- ecran peut en decouvrir a l'usage, quand le client les cree tard.
function Panes.Own(hote, id, cadre)
local h = hotes[hote]
local contenu = h and h.contenus[id]
if not contenu or not cadre then
return
end
for _, connu in ipairs(contenu.cadres) do
if connu == cadre then
return
end
end
contenu.cadres[#contenu.cadres + 1] = cadre
end
-- Le mobilier d'un groupe. Appelable plusieurs fois : les cadres s'ajoutent.
function Panes.Furniture(nom, groupe, cadres)
local hote = hotes[nom]
if not hote then
return
end
local liste = hote.mobilier[groupe] or {}
hote.mobilier[groupe] = liste
for _, cadre in ipairs(cadres or {}) do
if cadre then
liste[#liste + 1] = cadre
end
end
end
local function poser(contenu, visible)
if visible then
batir(contenu)
elseif not contenu.bati then
-- Jamais ouvert, donc rien a masquer : on ne le batit pas pour cela.
return
end
if contenu.racine then
if visible then contenu.racine:Show() else contenu.racine:Hide() end
end
for _, cadre in ipairs(contenu.cadres) do
if cadre and cadre.Show then
if visible then cadre:Show() else cadre:Hide() end
end
end
end
-- ------------------------------------------------------------ l'affichage
local function appliquer(hote)
local voulu = hote.montre and hote.pages[hote.groupe or ""] or nil
for _, id in ipairs(hote.ordre) do
poser(hote.contenus[id], id == voulu)
end
hote.actuel = voulu
-- LE MOBILIER. On masque tout ce qui est declare, puis on remontre celui
-- du groupe courant : un meuble partage par deux groupes ne depend ainsi
-- d'aucun ordre de parcours.
local actifs = {}
local sien = hote.mobilier[hote.groupe or ""]
if hote.montre and voulu and sien then
for _, cadre in ipairs(sien) do
actifs[cadre] = true
end
end
for _, liste in pairs(hote.mobilier) do
for _, cadre in ipairs(liste) do
if cadre and cadre.Hide and not actifs[cadre] then
cadre:Hide()
end
end
end
for cadre in pairs(actifs) do
if cadre.Show then
cadre:Show()
end
end
-- UN HOTE SANS CONTENU NE S'AFFICHE PAS. C'est ce qui empeche un volet
-- droit vide -- et son fond, et sa bande de pierre -- de rester a
-- l'ecran sur un onglet qui n'a rien a y mettre.
if hote.cadre then
local utile = hote.montre and voulu ~= nil
if utile then hote.cadre:Show() else hote.cadre:Hide() end
end
end
-- Ouvrir un groupe : un ecran entier, tous les hotes a la fois.
function Panes.ShowGroup(groupe)
for _, nom in ipairs(ordreHotes) do
local hote = hotes[nom]
hote.groupe = groupe
appliquer(hote)
end
Panes.groupe = groupe
end
function Panes.CurrentGroup()
return Panes.groupe
end
-- Changer de page dans un hote, sans toucher au groupe : c'est ce que font
-- deux boutons qui se partagent la meme surface.
function Panes.ShowPage(nom, id)
local hote = hotes[nom]
if not hote or not hote.contenus[id] then
return
end
hote.pages[hote.groupe or ""] = id
appliquer(hote)
end
function Panes.CurrentPage(nom)
local hote = hotes[nom]
return hote and hote.actuel
end
-- Masquer un hote entier -- le repli d'un volet. Le groupe et la page
-- choisie sont conserves : deplier les retrouve.
function Panes.SetHostShown(nom, etat)
local hote = hotes[nom]
if not hote then
return
end
hote.montre = etat and true or false
appliquer(hote)
end
function Panes.IsHostShown(nom)
local hote = hotes[nom]
return hote ~= nil and hote.montre and hote.actuel ~= nil
end
-- Un hote a-t-il quelque chose a montrer pour ce groupe ? Repond sans rien
-- batir : c'est ce qui decide, par exemple, si le bouton de repli a un sens.
function Panes.HasContent(nom, groupe)
local hote = hotes[nom]
return hote ~= nil and hote.pages[groupe or hote.groupe or ""] ~= nil
end
-- Reposer l'etat courant. A appeler quand un contenu vient d'etre declare
-- apres l'ouverture du groupe, jamais dans une boucle d'affichage.
function Panes.Refresh()
for _, nom in ipairs(ordreHotes) do
appliquer(hotes[nom])
end
end
-- TEMOIN. Ce que chaque hote montre, et ce qu'il possede.
function Panes.Report()
local lignes = {}
for _, nom in ipairs(ordreHotes) do
local hote = hotes[nom]
local pages = {}
for _, id in ipairs(hote.ordre) do
local contenu = hote.contenus[id]
if contenu.groupe == hote.groupe then
pages[#pages + 1] = string.format("%s(%d cadres%s)", id,
#contenu.cadres, contenu.bati and "" or ", jamais bati")
end
end
lignes[#lignes + 1] = string.format(
"%s : groupe=%s page=%s visible=%s | pages du groupe : %s",
nom, tostring(hote.groupe), tostring(hote.actuel),
tostring(hote.montre), table.concat(pages, ", "))
end
return lignes
end