Files
moon-well-ui/data/addon/ForeverUI/SpellBook.lua
T
Adrestie 400a3dc1da Grimoire : grille camelot sur cases securisees, combat, menus volants, reglages
- Livre de camelot dans SpellBookFrame : deux pages ou une, categories en
  onglets-icones, grille condensee, en-tete repete, portrait cuit rond.
- Menus volants : groupes de SpellFlyout.db2, completes des sorts de BC et
  WotLK, un groupe par onglet et par faction ; Seals et Judgements en plus.
- Combat : controleur securise ; onglets, pages, roulette, groupes,
  agrandir/reduire et les trois reglages passent par des blocs securises,
  les huit jeux de reglages publies hors combat.
- Banc : modele de securite de 3.3.5, OnShow/OnHide propages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:19:07 +02:00

1714 lines
66 KiB
Lua

-- ForeverUI : le grimoire de camelot (docs/GRIMOIRE.md, etape 1 : le cadre,
-- les categories et la grille, hors combat).
--
-- L'OSSATURE RESTE CELLE DE WOTLK. SpellBookFrame demeure le panneau que
-- ToggleSpellBook, la touche P et le micro-bouton ouvrent et ferment ; sa
-- croix (SpellBookCloseButton) reste la sienne, rhabillee, pour que la
-- fermeture ne passe pas par notre code. Son art et ses douze boutons de sort
-- s'effacent ; le livre de camelot se pose DANS SpellBookFrame, et s'ouvre
-- et se ferme avec lui.
--
-- LANCER UN SORT EST PROTEGE. Les douze boutons de WotLK lancent depuis
-- l'etat interne de son grimoire (type de livre, ligne, page) : la grille de
-- camelot ne peut pas s'y adosser. Chaque case est donc un bouton SECURISE
-- (SecureActionButtonTemplate, type "spell").
--
-- EN COMBAT (etape 2). Un sort ne se change sur une case que par du code
-- securise. Hors combat, TOUT est calcule -- chaque categorie, chaque page,
-- chaque groupe -- et publie dans un controleur securise
-- (SecureHandlerAttributeTemplate). Les onglets, les fleches, la roulette et
-- les groupes ne font que lui dire "categorie n" ou "tourne de 1" ; son bloc
-- "afficher" pose alors les cases (attributs, place, visibilite), puis rend
-- la main par CallMethod a du code ordinaire qui change images et textes --
-- ce que le client permet en combat. Le meme chemin sert hors combat : il n'y
-- en a qu'un.
--
-- Contraintes du client (RestrictedExecution.lua, RestrictedFrames.lua) :
-- un bloc securise ne contient ni accolade ni le mot "function" (d'ou
-- newtable) ; SetPoint n'y vise qu'un cadre explicitement protege, ou
-- "$parent". Ce qui reste impossible en combat : le calcul lui-meme (un sort
-- appris, un familier appele pendant le combat attendent sa fin). Agrandir ou
-- reduire, et les trois reglages, passent en combat : chaque combinaison
-- (8 jeux de reglages x 2 modes) est publiee a l'avance, et le menu des
-- reglages est fait de boutons securises.
--
-- RELEVE -- blizzard_playerspells (le .toc : camelot/ pour le cadre et le
-- grimoire, spellbook/ pour le reste) :
-- PlayerSpellsFrame PortraitFrameTemplate ; 1618 x 720 (deux pages),
-- 809 x 720 (une page) ; panneau "center" : TOP de
-- l'ecran a -116 ; titre SPELLBOOK ; portrait : l'icone
-- de la ligne General ; bouton agrandir / reduire a
-- gauche de la croix (MaximizeMinimizeButtonFrameTemplate)
-- SpellBookFrame BOTTOMLEFT (0, 4), 1612 (806) x 702 ; pages
-- spellbook-page-left / -right (c60) chacune sur une
-- moitie, une seule (-right) en reduit
-- CategoryTabSystem TOPLEFT (70, -26), onglets-icones en haut (44 x 32,
-- ecart 1) : icone 36 x 35 centree, cadre
-- spellbook-tab-frame-c60 en BOTTOM (0, 1) PAR-DESSUS
-- l'icone ; choisi : -glow-c60 et -glow-gradient-c60
-- PagedSpellsFrame TOPLEFT (0, -50) ; deux vues de 680 x 590, a
-- (85, -45) et TOPRIGHT (-50, -45) ; 3 colonnes, ecarts
-- 15 x 10 ; en-tete 51 de haut, sort 60 ; les colonnes
-- se remplissent l'une apres l'autre, sur le moins de
-- rangees possible (PagedCondensedVerticalGrid)
-- PagingControls BOTTOMRIGHT (-75, 40) : "Page n/m", fleche
-- precedente, fleche suivante, ecart 8 ; SystemFont_Med3
-- a l'encre du grimoire
-- en-tete nom en SystemFont_Huge2 de (-8, 0) a (-60, 0) ;
-- separateur spellbook-divider (11) de (-32) a (-60) ;
-- fond spellbook-list-backplate 416 x 106 a LEFT
-- (-85, 10), alpha 0,65
-- sort icone 36 dans un bouton 40 x 40 a LEFT ; cadre carre
-- spellbook-item-iconframe (-11, 1)/(1, -7), rond pour un
-- passif (talents-node-circle-gray) ; ombre (-12, 3)/(2, -8) ;
-- nom SystemFont_Large, sous-titre SystemFont_Med1 (le
-- rang, ou "Passive"), a (50, -1) ; fond
-- spellbook-item-backplate a 0,25, 1 au survol ; survol
-- de l'icone en ADD a 0,35, 0,65 presse
--
-- CE QUI DIFFERE, ET POURQUOI.
-- * 3.3.5 n'a pas de masque : l'icone carree n'a pas ses coins arrondis ;
-- l'icone d'un passif s'arrondit par SetPortraitToTexture.
-- * 3.3.5 n'a pas de sorts "a venir" : aucun sort grise ni "disponible au
-- niveau n".
-- * le familier : ses sorts se lancent par leur nom, et le clic droit
-- bascule leur lancement automatique (/petautocasttoggle).
-- * le panneau de WotLK reste de zone "left" : le livre se pose ou camelot
-- pose un panneau "center", sans deplacer les autres.
ForeverUI = ForeverUI or {}
local S = {}
ForeverUI.SpellBook = S
local G = {
largeur = 1618, largeurReduite = 809, hauteur = 720, haut = -116,
livreL = 1612, livreLReduit = 806, livreH = 702, livreBas = 4,
ongletsX = 70, ongletsY = -26, ongletL = 44, ongletH = 32, ongletEcart = 1,
iconeOngletL = 34, iconeOngletH = 33,
pagesHaut = -50, vueL = 680, vueH = 590, vue1X = 85, vueY = -45, vue2X = -50,
colonnes = 3, ecartX = 15, ecartY = 10, enteteH = 51, sortH = 60,
pagerX = -75, pagerY = 40, pagerEcart = 8, flecheCote = 32,
boutonCote = 40, iconeCote = 36, texteX = 50,
casesParVue = 24,
fermerX = -2, fermerY = 1, rougeCote = 24,
portraitX = -5, portraitY = 7, portraitCote = 62,
titreX1 = 58, titreX2 = -24, titreY = -1, titreH = 20,
reglagesX = -30, reglagesY = -27, reglagesL = 15, reglagesH = 16,
-- le menu volant (SpellBookItemButtonMixin, FlyoutButtonTemplate,
-- SpellFlyout) : ouvert a DROITE, decale de -4, 42 de haut ; fleche 15 x 6,
-- a 4 du bord ferme, 2 ouvert ; petits boutons de 30, premier a 9, ecart 4,
-- 9 apres le dernier ; bordure teintee a 0,7
volantDecalage = -4, volantH = 42, flecheL = 15, flecheH = 6,
flecheFerme = 4, flecheOuvert = 2, petitCote = 30, volantDebut = 9,
volantEcart = 4, volantFin = 9, volantTeinte = 0.7,
petitCadre = 35, petitEtatL = 31.6, petitEtatH = 30.9,
}
G.celluleL = (G.vueL - G.ecartX * (G.colonnes - 1)) / G.colonnes
-- les coins de metal : PortraitFrameTemplate, corrige par camelot (haut-droit
-- et bas-droit x - 2, bas y = -8)
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 },
}
local ENCRE = { 0.18, 0.106, 0.059 } -- SPELLBOOK_FONT_COLOR
local POLICE = "Fonts\\FRIZQT__.TTF"
local TEXTE = {
titre = SPELLBOOK or "Spellbook",
page = "Page %d/%d", -- PAGE_NUMBER_WITH_MAX
passif = SPELL_PASSIVE or "Passive",
familier = PET or "Pet",
-- les reglages : SPELLBOOK_FILTER_PASSIVES et SHOW_ALL_SPELL_RANKS de camelot
masquerPassifs = "Hide Passives",
tousLesRangs = "Show all spell ranks",
volants = "Group Similar Spells on Flyouts", -- SPELLBOOK_USE_FLYOUTS
}
-- LE MENU DES REGLAGES, en boutons securises. Il reprend la liste du client
-- telle que DropDown.lua l'habille (UIDropDownMenu.lua et
-- UIDropDownMenuTemplates.xml de 3.3.5, releve) : strate DIALOG, ancree
-- TOPLEFT sur le BOTTOMLEFT de la fleche (ToggleDropDownMenu, decalage 0) ;
-- lignes de 20 (UIDROPDOWNMENU_BUTTON_HEIGHT de ForeverUI) a x = 5 + 12 - 6
-- (case, mode MENU) et y = -15 - (n - 1) x 20 ; liste de n x 20 + 2 x 15 de
-- haut et de "texte le plus long + 40 + 25" de large, lignes 25 plus
-- etroites ; texte GameFontHighlightLeft a 20 du bord ; case
-- common-dropdown-ticksquare 12 a gauche, coche jaune 15 x 14 a (2, 1) ;
-- surbrillance UI-QuestTitleHighlight en ADD ; fond common-dropdown-bg-c60
-- en neuf tranches (18 ; 9, 6, 9, 12) a 0,925. Fermeture : 2 s apres la sortie
-- de la souris (UIDROPDOWNMENU_SHOW_TIME), ou la fleche.
local MENU = {
ligneH = 20, bord = 15, ligneX = 11, texteX = 20, largeurPlus = 40, marge = 25,
case = 12, cocheL = 15, cocheH = 14, cocheX = 2, cocheY = 1,
fondCoin = 18, fondMarges = { 9, 6, 9, 12 }, fondAlpha = 0.925, attente = 2,
}
-- les reglages et leur bit dans la combinaison (REG) ; "volants" est coche
-- quand son bit est ABSENT (le bit dit "sans groupes")
local REGLAGES = {
{ bit = 1, texte = TEXTE.masquerPassifs },
{ bit = 2, texte = TEXTE.volants, inverse = true },
{ bit = 4, texte = TEXTE.tousLesRangs },
}
local SEP = string.char(92)
local ROCHE = "interface" .. SEP .. "ForeverUI" .. SEP .. "framegeneral" .. SEP .. "ui-background-rock"
local FLECHES = "Interface" .. SEP .. "Buttons" .. SEP .. "UI-SpellbookIcon-"
local SURVOL_CARRE = "Interface" .. SEP .. "Buttons" .. SEP .. "UI-Common-MouseHilight"
local ICONE_CLASSE = "Interface" .. SEP .. "ForeverUI" .. SEP .. "icons" .. SEP .. "classicon_"
-- le portrait : l'icone de la ligne General de camelot (inv_misc_book_09),
-- son masque rond cuit dedans (tools/cuire_masque.py)
local PORTRAIT = "Interface" .. SEP .. "ForeverUI" .. SEP .. "spellbook" .. SEP .. "portrait"
local function reglages()
ForeverUIDB = ForeverUIDB or {}
ForeverUIDB.grimoire = ForeverUIDB.grimoire or {}
return ForeverUIDB.grimoire
end
local function police(fs, taille, c)
fs:SetFont(POLICE, taille)
c = c or ENCRE
fs:SetTextColor(c[1], c[2], c[3])
end
-- ETOUFFER : image, opacite, visibilite, souris -- le code de WotLK remontre
-- ce qu'on se contente de cacher.
local function etouffer(f)
if not f then return end
if f.SetTexture then f:SetTexture(nil) end
f:SetAlpha(0)
if f.EnableMouse then f:EnableMouse(false) end
f:Hide()
end
-- ---------------------------------------------------------------- les donnees
-- Les categories de camelot : une par ligne de sorts (Generale, puis une par
-- arbre), plus le familier. La Generale porte l'icone de la classe.
local function categories()
local liste = {}
for i = 1, GetNumSpellTabs() do
local nom, icone, decalage, nombre = GetSpellTabInfo(i)
if nom then
if i == 1 then
local _, classe = UnitClass("player")
icone = ICONE_CLASSE .. string.lower(classe or "warrior")
end
table.insert(liste, { nom = nom, icone = icone, decalage = decalage, nombre = nombre, livre = BOOKTYPE_SPELL })
end
end
local nombrePet = HasPetSpells()
if nombrePet and nombrePet > 0 then
table.insert(liste, {
nom = TEXTE.familier, icone = GetPetIcon and GetPetIcon() or nil,
decalage = 0, nombre = nombrePet, livre = BOOKTYPE_PET, familier = true,
})
end
return liste
end
S.categories = categories
-- Les sorts d'une categorie. Sans "tous les rangs" (CVar ShowAllSpellRanks),
-- un sort ne garde que son rang le plus haut -- le dernier de la ligne.
-- "Masquer les passifs" (spellBookHidePassives chez camelot, que 3.3.5 n'a
-- pas : retenu dans les reglages de ForeverUI) les retire.
-- `opts` (facultatif) : { passifs = masquer, volants = sans groupes,
-- rangs = tous } -- par defaut, les reglages en cours.
local function optionsCourantes()
return { passifs = reglages().masquerPassifs and true or false,
volants = reglages().sansVolants and true or false,
rangs = GetCVar("ShowAllSpellRanks") == "1" }
end
local function sortsDe(cat, opts)
opts = opts or optionsCourantes()
local sorts, dernier = {}, {}
local tousLesRangs = opts.rangs
local sansPassifs = opts.passifs
for slot = cat.decalage + 1, cat.decalage + cat.nombre do
local nom, rang = GetSpellName(slot, cat.livre)
local passif = nom and IsPassiveSpell(slot, cat.livre) and true or false
if nom and not (sansPassifs and passif) then
local sort = { slot = slot, livre = cat.livre, nom = nom, rang = rang or "", passif = passif,
icone = GetSpellTexture(slot, cat.livre), familier = cat.familier }
if not tousLesRangs and dernier[nom] then
sorts[dernier[nom]] = sort
else
table.insert(sorts, sort)
dernier[nom] = #sorts
end
end
end
return sorts
end
S.sortsDe = sortsDe
-- ------------------------------------------------------------ les menus volants
-- "Group Similar Spells on Flyouts" (SPELLBOOK_USE_FLYOUTS). Chez camelot, le
-- serveur place dans le grimoire une entree "flyout" ; ses sorts en sont
-- retires (IsSpellBookItemLooseFlyoutMember) et s'ouvrent dans un menu
-- volant. 3.3.5 n'a ni entree ni menu : les groupes sont ceux du client
-- camelot, releves dans SpellFlyout.db2 et SpellFlyoutItem.db2 (nom,
-- description, icone, sorts dans l'ordre de leurs emplacements). N'y sont
-- pas : les deux groupes vides (263, 265) et les deux dont tous les sorts sont
-- propres a camelot (252 Spell Resistances, 257 Weapon Proficiencies).
--
-- APRES chaque liste de camelot, les sorts de classe que Burning Crusade et
-- WotLK ajoutent a ces familles, verifies dans le Spell.dbc du serveur et
-- rattaches a une ligne de classe (SkillLineAbility.dbc).
--
-- Un sort appartient a un groupe PAR SON NOM : GetSpellInfo(id) le donne pour
-- chaque identifiant releve, et les rangs que 3.3.5 ajoute (Burning Crusade,
-- WotLK) suivent ainsi le rang d'origine. Les identifiants propres a camelot
-- (au-dela d'un million) n'existent pas ici et ne comptent pas.
local VOLANTS = {
-- Portails et teleportations : un groupe par faction (le masque de race de
-- SpellFlyout). APRES la liste de camelot, les villes que Burning Crusade
-- et WotLK ajoutent, verifiees dans le Spell.dbc du serveur : Exodar /
-- Silvermoon, Shattrath (un sort par faction), Theramore / Stonard,
-- Dalaran (le meme pour les deux).
{ id = 248, nom = "Portal", icone = "spell_arcane_portalstormwind", faction = "Alliance",
texte = "Creates a portal, teleporting group members who use it to a major city.",
sorts = { 11419, 11416, 10059, 32266, 33691, 49360, 53142 } },
{ id = 249, nom = "Portal", icone = "spell_arcane_portalorgrimmar", faction = "Horde",
texte = "Creates a portal, teleporting group members who use it to a major city.",
sorts = { 11417, 11420, 11418, 32267, 35717, 49361, 53142 } },
{ id = 250, nom = "Teleport", icone = "spell_arcane_teleportstormwind", faction = "Alliance",
texte = "Teleports you to a major city.",
sorts = { 3565, 3562, 3561, 1297659, 32271, 33690, 49359, 53140 } },
{ id = 251, nom = "Teleport", icone = "spell_arcane_teleportorgrimmar", faction = "Horde",
texte = "Teleports you to a major city.",
sorts = { 3567, 3566, 3563, 1297659, 32272, 35715, 49358, 53140 } },
{ id = 253, nom = "Aspect", icone = "ability_hunter_aspectmastery",
texte = "Take on aspects of nature.",
sorts = { 13163, 13165, 14318, 14319, 14320, 14321, 14322, 25296, 5118, 13161,
1299445, 1299446, 1299447, 13159, 20043, 20190,
34074, 61846 } }, -- + Viper, Dragonhawk
{ id = 259, nom = "Summon Demon", icone = "spell_shadow_summonimp",
texte = "Summons a Demon under the command of the Warlock",
sorts = { 688, 697, 712, 713, 691,
30146 } }, -- + Felguard
{ id = 261, nom = "Imp Spells", icone = "spell_fire_firebolt",
texte = "[PH] Commands the Imp to cast spells",
sorts = { 20801, 6307, 2949, 4511 } },
{ id = 262, nom = "Shapeshift", icone = "ability_racial_bearform",
texte = "Shapeshift into a different form.",
sorts = { 5487, 9634, 1066, 768, 783, 24858,
33943, 40120, 33891 } }, -- + Flight, Swift Flight, Tree of Life
{ id = 264, nom = "Blessings", icone = "spell_magic_magearmor",
texte = "Places a Blessing on friendly targets.",
-- 1038 (Blessing of Salvation chez camelot) est devenu Hand of
-- Salvation : il passe avec les autres Hand, dans Utility Blessings
sorts = { 20217, 25291, 19838, 19837, 19836, 19835, 19834, 19740, 25290,
19854, 19853, 19852, 19850, 19742, 19979, 19978, 19977,
20911 } }, -- + Sanctuary
{ id = 267, nom = "Pet Utilities", icone = "ability_hunter_beasttaming",
texte = "Manage your pets.",
sorts = { 883, 982, 6991, 2641, 1515, 1462, 5149 } },
{ id = 268, nom = "Tracking", icone = "ability_tracking",
texte = "Track your quarry.",
sorts = { 1494, 19878, 19879, 19880, 19882, 19885, 19883, 19884 } },
{ id = 269, nom = "Stances", icone = "ability_warrior_offensivestance",
texte = "Activate a combat stance.",
sorts = { 2457, 71, 2458 } },
{ id = 270, nom = "Auras", icone = "spell_holy_devotionaura",
texte = "Activate a protective aura for your group.",
sorts = { 10301, 10300, 10299, 10298, 7294, 10293, 10292, 1032, 10291, 643,
10290, 465, 19746, 20218, 19900, 19899, 19891, 19898, 19897, 19888,
19896, 19895, 19876,
32223 } }, -- + Crusader Aura
{ id = 272, nom = "Greater Blessings", icone = "spell_holy_greaterblessingofkings",
texte = "Blessings that target all members of your group that share the same class.",
sorts = { 25895, 25898, 25916, 25782, 25918, 25894, 25890,
25899 } }, -- + Greater Sanctuary
{ id = 273, nom = "Utility Blessings", icone = "spell_holy_sealofvalor",
texte = "Short duration Blessings used to save a single target.",
sorts = { 10278, 5599, 1022, 1044, 20729, 6940,
1038 } }, -- + Hand of Salvation
-- DEUX GROUPES QUE CAMELOT N'A PAS (demande du 2026-09-25) : les sceaux et
-- les jugements du paladin, que son client classique ne groupe pas. Ni
-- identifiant, ni description, ni icone propre : le groupe prend celle
-- de son premier sort. Sorts verifies dans Spell.dbc et SkillLineAbility.
{ nom = "Seals",
sorts = { 21084, 20164, 20165, 20166, 20375, 31801, 53736 } },
{ nom = "Judgements",
sorts = { 20271, 53408, 53407 } },
}
S.VOLANTS = VOLANTS
-- nom du sort -> { groupe, rang du nom dans le groupe }, resolu une fois
-- (un nom peut servir a deux groupes : Dalaran est dans ceux des deux
-- factions ; on garde celui de la faction du joueur)
local parNom
local function volantDe(nom)
if not parNom then
parNom = {}
for _, v in ipairs(VOLANTS) do
local ordre, vus = 0, {}
for _, id in ipairs(v.sorts) do
local n = GetSpellInfo(id)
if n and not vus[n] then
vus[n] = true
ordre = ordre + 1
parNom[n] = parNom[n] or {}
table.insert(parNom[n], { volant = v, ordre = ordre })
end
end
end
end
local faction = UnitFactionGroup("player")
for _, m in ipairs(parNom[nom] or {}) do
if not m.volant.faction or m.volant.faction == faction then
return m
end
end
end
-- Les groupes d'UNE categorie : un groupe ne reunit que des sorts d'un meme
-- onglet (les postures du guerrier, chacune dans son arbre, restent seules ;
-- les portails et teleportations du mage, tous dans le meme, se groupent).
-- Il lui faut au moins deux sorts DIFFERENTS -- deux rangs d'un meme sort ne
-- font pas un groupe. Ses sorts suivent l'ordre du groupe.
local function groupesDe(sorts)
local groupes = {}
for _, sort in ipairs(sorts) do
local m = volantDe(sort.nom)
if m then
local g = groupes[m.volant]
if not g then
g = { membres = {}, noms = {}, differents = 0 }
groupes[m.volant] = g
end
sort.ordre = m.ordre
table.insert(g.membres, sort)
if not g.noms[sort.nom] then
g.noms[sort.nom] = true
g.differents = g.differents + 1
end
end
end
for v, g in pairs(groupes) do
if g.differents < 2 then
groupes[v] = nil
else
table.sort(g.membres, function(a, b)
if a.ordre ~= b.ordre then return a.ordre < b.ordre end
return a.slot < b.slot
end)
end
end
return groupes
end
-- Ce que montre une categorie : ses sorts, et -- si l'on groupe -- chaque
-- groupe a la place de son premier sort, ses membres retires. Le familier
-- n'est pas groupe.
local function listeAffichee(cats, index, opts)
opts = opts or optionsCourantes()
local cat = cats[index]
if not cat then return {} end
local sorts = sortsDe(cat, opts)
if cat.familier or opts.volants then
return sorts
end
local groupes = groupesDe(sorts)
local liste, poses = {}, {}
for _, sort in ipairs(sorts) do
local m = volantDe(sort.nom)
local g = m and groupes[m.volant]
if not g then
table.insert(liste, sort)
elseif not poses[m.volant] then
poses[m.volant] = true
local v = m.volant
local icone = v.icone and ("Interface" .. SEP .. "Icons" .. SEP .. v.icone) or g.membres[1].icone
table.insert(liste, { volant = v, nom = v.nom, rang = "", passif = false,
icone = icone, membres = g.membres })
end
end
return liste
end
S.listeAffichee = listeAffichee
-- ------------------------------------------------------------ la mise en page
-- PagedCondensedVerticalGrid : l'en-tete prend sa rangee -- et la reprend en
-- tete de chaque vue suivante de la categorie --, puis les sorts
-- remplissent les colonnes une a une sur ceil(restants / 3) rangees, dans la
-- place qui reste ; une vue pleine en ouvre une autre, et le compte des
-- rangees repart des sorts restants. Rend la liste des vues, chacune une
-- liste d'elements { en-tete ou sort, colonne, y }.
local function mettreEnPage(titre, sorts)
local vues = {}
local vue, occupe
local function nouvelleVue()
vue = {}
table.insert(vues, vue)
occupe = 0
if titre then
table.insert(vue, { entete = titre, colonne = 1, y = 0 })
occupe = G.enteteH + G.ecartY
end
end
nouvelleVue()
local pas = G.sortH + G.ecartY
local i = 1
while i <= #sorts do
local dispo = math.floor((G.vueH - occupe) / pas)
if dispo < 1 then
nouvelleVue()
dispo = math.floor((G.vueH - occupe) / pas)
end
local restants = #sorts - i + 1
local rangees = math.min(math.ceil(restants / G.colonnes), dispo)
for colonne = 1, G.colonnes do
for r = 1, rangees do
if i <= #sorts then
table.insert(vue, { sort = sorts[i], colonne = colonne, y = occupe + (r - 1) * pas })
i = i + 1
end
end
end
occupe = occupe + rangees * pas
if i <= #sorts then
nouvelleVue()
end
end
return vues
end
S.mettreEnPage = mettreEnPage
-- --------------------------------------------------------------- les cases
local function atlas(t, nom, garder)
return ForeverUI.SetAtlas(t, nom, garder)
end
-- un sort : un cadre porteur (fond, textes) et, dedans, le bouton securise
-- de l'icone
local function creerCase(vue, n)
local c = CreateFrame("Frame", nil, vue)
c:SetWidth(G.celluleL)
c:SetHeight(G.sortH)
local fond = c:CreateTexture(nil, "BACKGROUND")
atlas(fond, "spellbook-item-backplate")
fond:SetPoint("CENTER", c, "CENTER", 5, -5)
fond:SetAlpha(0.25)
c.fond = fond
local b = CreateFrame("Button", "ForeverUISpellBookButton" .. n, c, "SecureActionButtonTemplate")
b:SetWidth(G.boutonCote)
b:SetHeight(G.boutonCote)
b:SetPoint("LEFT", c, "LEFT", 0, 0)
b:RegisterForClicks("AnyUp")
b:RegisterForDrag("LeftButton")
-- le numero de la case : le bloc securise des groupes le rend a
-- ForeverUIVolant
b:SetID(n)
-- le clic modifie donne le lien dans la discussion, pas le sort
b:SetAttribute("shift-type1", "lien")
local icone = b:CreateTexture(nil, "ARTWORK")
icone:SetWidth(G.iconeCote)
icone:SetHeight(G.iconeCote)
icone:SetPoint("CENTER", b, "CENTER", 0, 0)
local ombre = b:CreateTexture(nil, "ARTWORK")
atlas(ombre, "spellbook-item-iconframe-shadow", true)
ombre:SetPoint("TOPLEFT", b, "TOPLEFT", -12, 3)
ombre:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 2, -8)
local cadre = b:CreateTexture(nil, "OVERLAY")
local survol = b:CreateTexture(nil, "OVERLAY")
survol:SetAllPoints(b)
survol:SetBlendMode("ADD")
survol:SetAlpha(0.35)
survol:Hide()
local auto = b:CreateTexture(nil, "OVERLAY")
atlas(auto, "spellbook-item-petautocast-corners", true)
auto:SetAllPoints(icone)
auto:Hide()
local recharge = CreateFrame("Cooldown", "ForeverUISpellBookButton" .. n .. "Cooldown", b, "CooldownFrameTemplate")
recharge:SetPoint("TOPLEFT", icone, "TOPLEFT", 2, -2)
recharge:SetPoint("BOTTOMRIGHT", icone, "BOTTOMRIGHT", -2, 2)
-- la fleche d'un menu volant (FlyoutButtonTemplate, Arrow)
local fleche = b:CreateTexture(nil, "OVERLAY")
fleche:Hide()
b.icone, b.ombre, b.cadre, b.survol, b.auto, b.recharge = icone, ombre, cadre, survol, auto, recharge
b.fleche = fleche
-- les textes
local nom = c:CreateFontString(nil, "ARTWORK")
police(nom, 16)
nom:SetJustifyH("LEFT")
nom:SetWidth(G.celluleL - G.texteX)
local sous = c:CreateFontString(nil, "ARTWORK")
police(sous, 12)
sous:SetJustifyH("LEFT")
sous:SetWidth(G.celluleL - G.texteX)
c.nom, c.sous = nom, sous
b:SetScript("OnEnter", function(self)
c.fond:SetAlpha(1)
self.survol:Show()
self.survole = true
local sort = c.sort
if sort and sort.volant then
-- le groupe : son nom et sa description (SpellFlyout.db2)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(sort.nom, 1, 1, 1)
if sort.volant.texte then GameTooltip:AddLine(sort.volant.texte, nil, nil, nil, 1) end
GameTooltip:Show()
S.poserFleche(self)
elseif sort then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetSpell(sort.slot, sort.livre)
GameTooltip:Show()
end
end)
b:SetScript("OnLeave", function(self)
self.survole, self.enfonce = nil, nil
c.fond:SetAlpha(0.25)
self.survol:Hide()
GameTooltip:Hide()
if c.sort and c.sort.volant then S.poserFleche(self) end
end)
b:SetScript("OnMouseDown", function(self)
self.enfonce = true
self.survol:SetAlpha(0.65)
if c.sort and c.sort.volant then S.poserFleche(self) end
end)
b:SetScript("OnMouseUp", function(self)
self.enfonce = nil
self.survol:SetAlpha(0.35)
if c.sort and c.sort.volant then S.poserFleche(self) end
end)
b:SetScript("OnDragStart", function(self)
local sort = c.sort
-- un groupe ne va pas sur une barre : 3.3.5 n'y a pas de menu volant
if sort and not sort.volant then PickupSpell(sort.slot, sort.livre) end
self.enfonce = nil
end)
-- SpellButton_OnModifiedClick : le lien du sort. Un groupe s'ouvre par le
-- bloc securise enveloppe autour de OnClick (S.publier).
b:SetScript("PostClick", function(self, souris)
local sort = c.sort
if sort and not sort.volant and IsModifiedClick("CHATLINK") then
local lien = GetSpellLink(sort.slot, sort.livre)
if lien then ChatEdit_InsertLink(lien) end
end
end)
c.bouton = b
c:Hide()
return c
end
-- UpdateVisuals : cadre carre ou rond, textes, recharge, lancement
-- automatique. Rien de protege : appele en combat par ForeverUIVisuels.
local function remplirCase(c, sort)
c.sort = sort
local b = c.bouton
if sort.passif then
SetPortraitToTexture(b.icone, sort.icone)
b.icone:SetTexCoord(0, 1, 0, 1)
atlas(b.cadre, "talents-node-circle-gray", true)
b.cadre:ClearAllPoints()
b.cadre:SetAllPoints(b)
atlas(b.survol, "spellbook-item-iconframe-passive-hover", true)
-- un passif n'a que son icone ronde : pas l'ombre carree d'un actif
b.ombre:Hide()
else
b.ombre:Show()
b.icone:SetTexture(sort.icone)
b.icone:SetTexCoord(0, 1, 0, 1)
atlas(b.cadre, "spellbook-item-iconframe-c60", true)
b.cadre:ClearAllPoints()
b.cadre:SetPoint("TOPLEFT", b, "TOPLEFT", -11, 1)
b.cadre:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 1, -7)
atlas(b.survol, "spellbook-item-iconframe-hover", true)
end
c.nom:SetText(sort.nom)
local sousTitre = sort.rang
if sousTitre == "" and sort.passif then sousTitre = TEXTE.passif end
c.sous:SetText(sousTitre)
-- le bloc de texte, centre sur la case a (50, -1)
local hNom = c.nom:GetHeight() or 0
local hSous = (sousTitre ~= "" and (c.sous:GetHeight() or 0)) or 0
local total = hNom + (hSous > 0 and (2 + hSous) or 0)
c.nom:ClearAllPoints()
c.nom:SetPoint("TOPLEFT", c, "LEFT", G.texteX, total / 2 - 1)
c.sous:ClearAllPoints()
c.sous:SetPoint("TOPLEFT", c.nom, "BOTTOMLEFT", 0, -2)
if sousTitre == "" then c.sous:Hide() else c.sous:Show() end
if sort.volant then
b.fleche:Show()
S.poserFleche(b)
else
b.fleche:Hide()
end
S.majEtat(c)
end
-- "Nom(Rang N)" : ce que SecureActionButton passe a CastSpellByName
function S.texteDuSort(sort)
local texte = sort.nom
if sort.rang ~= "" and not sort.familier then texte = texte .. "(" .. sort.rang .. ")" end
return texte
end
-- la recharge et le lancement automatique, sans toucher aux attributs
function S.majEtat(c)
local sort = c.sort
if not sort then return end
local b = c.bouton
if sort.volant then
if CooldownFrame_SetTimer then CooldownFrame_SetTimer(b.recharge, 0, 0, 0) end
b.auto:Hide()
return
end
local debut, duree, actif = GetSpellCooldown(sort.slot, sort.livre)
if CooldownFrame_SetTimer and debut then
CooldownFrame_SetTimer(b.recharge, debut, duree, actif)
end
if sort.familier then
local permis, allume = GetSpellAutocast(sort.slot, sort.livre)
if permis and allume then b.auto:Show() else b.auto:Hide() end
else
b.auto:Hide()
end
end
-- ------------------------------------------------------------ le menu volant
-- SetClampedTextureRotation a 90 ou 270, sur un element d'atlas : la forme a
-- huit arguments de SetTexCoord (SetRotation effacerait le rectangle), les
-- cotes echanges.
local function tournerAtlas(t, nom, degres, largeur, hauteur)
local e = ForeverUI.AtlasEntry(nom)
if not e then return end
t:SetTexture(e[1])
local u1, u2, v1, v2 = e[2], e[3], e[4], e[5]
if degres == 90 then
t:SetTexCoord(u1, v2, u2, v2, u1, v1, u2, v1)
else
t:SetTexCoord(u2, v1, u1, v1, u2, v2, u1, v2)
end
t:SetWidth(hauteur)
t:SetHeight(largeur)
end
-- FlyoutButtonMixin : fleche normale, survolee ou enfoncee ; tournee vers la
-- droite (90) ferme, retournee (270) ouvert, a 4 ou 2 du bord droit
function S.poserFleche(b)
local ouvert = S.volantOuvert == b
local nom = "ui-hud-actionbar-flyout"
if b.enfonce then
nom = nom .. "-down"
elseif b.survole then
nom = nom .. "-mouseover"
end
tournerAtlas(b.fleche, nom, ouvert and 270 or 90, G.flecheL, G.flecheH)
b.fleche:ClearAllPoints()
b.fleche:SetPoint("RIGHT", b, "RIGHT", ouvert and G.flecheOuvert or G.flecheFerme, 0)
end
-- SpellFlyoutPopupButtonTemplate : le petit bouton d'action (30, cadre et
-- enfonce 35 centres, survol 31,6 x 30,9), securise pour lancer
local function creerPetit(volant, n)
local b = CreateFrame("Button", "ForeverUISpellFlyoutButton" .. n, volant, "SecureActionButtonTemplate")
b:SetWidth(G.petitCote)
b:SetHeight(G.petitCote)
b:RegisterForClicks("AnyUp")
b:RegisterForDrag("LeftButton")
b:SetAttribute("type1", "spell")
b:SetAttribute("shift-type1", "lien")
-- sa place ne change jamais : le bloc securise ne fait que le montrer
b:SetPoint("LEFT", volant, "LEFT", G.volantDebut + (n - 1) * (G.petitCote + G.volantEcart), 0)
b:Hide()
local icone = b:CreateTexture(nil, "BORDER")
icone:SetAllPoints(b)
b.icone = icone
local function etat(t, nom, l, h, add)
ForeverUI.SetAtlas(t, nom, true)
t:SetWidth(l)
t:SetHeight(h)
t:ClearAllPoints()
t:SetPoint("CENTER", b, "CENTER", 0, 0)
if add then t:SetBlendMode("ADD") end
end
local e = ForeverUI.AtlasEntry("ui-hud-actionbar-iconframe")
b:SetNormalTexture(e and e[1] or "")
etat(b:GetNormalTexture(), "ui-hud-actionbar-iconframe", G.petitCadre, G.petitCadre)
b:SetPushedTexture(e and e[1] or "")
etat(b:GetPushedTexture(), "ui-hud-actionbar-iconframe-down", G.petitCadre, G.petitCadre)
b:SetHighlightTexture(e and e[1] or "")
etat(b:GetHighlightTexture(), "ui-hud-actionbar-iconframe-mouseover", G.petitEtatL, G.petitEtatH)
-- le COCHE : le survol en ADD, montre pendant que le sort est en cours
local coche = b:CreateTexture(nil, "OVERLAY")
etat(coche, "ui-hud-actionbar-iconframe-mouseover", G.petitEtatL, G.petitEtatH, true)
coche:Hide()
b.coche = coche
b.recharge = CreateFrame("Cooldown", "ForeverUISpellFlyoutButton" .. n .. "Cooldown", b, "CooldownFrameTemplate")
b.recharge:SetAllPoints(icone)
b:SetScript("OnEnter", function(self)
if not self.sort then return end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT", 4, 4)
GameTooltip:SetSpell(self.sort.slot, self.sort.livre)
GameTooltip:Show()
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
b:SetScript("OnDragStart", function(self)
if self.sort then PickupSpell(self.sort.slot, self.sort.livre) end
end)
-- SpellFlyoutPopupButtonMixin:OnClick : le lien ici ; le sort puis la
-- fermeture du menu passent par le bloc securise (S.publier)
b:SetScript("PostClick", function(self)
if self.sort and IsModifiedClick("CHATLINK") then
local lien = GetSpellLink(self.sort.slot, self.sort.livre)
if lien then ChatEdit_InsertLink(lien) end
end
end)
return b
end
local function majPetit(b)
local sort = b.sort
if not sort then return end
local debut, duree, actif = GetSpellCooldown(sort.slot, sort.livre)
if CooldownFrame_SetTimer and debut then
CooldownFrame_SetTimer(b.recharge, debut, duree, actif)
end
if IsCurrentSpell and IsCurrentSpell(sort.slot, sort.livre) then b.coche:Show() else b.coche:Hide() end
end
-- FlyoutPopupTemplate, ouvert a droite : le bout de depart (FlyoutBottom) a
-- gauche, le milieu (FlyoutMidLeft), le bout (FlyoutButton) a droite ; les
-- deux bouts tournes de 90
local function construireVolant(livre)
local volant = CreateFrame("Frame", "ForeverUISpellFlyout", livre)
volant:SetFrameStrata("DIALOG")
volant:EnableMouse(true)
volant:SetHeight(G.volantH)
volant:Hide()
local debut = volant:CreateTexture(nil, "BACKGROUND")
tournerAtlas(debut, "ui-hud-actionbar-iconframe-flyoutbottom", 90, 47, 5)
debut:SetHeight(G.volantH)
debut:SetPoint("LEFT", volant, "LEFT", 0, 0)
local fin = volant:CreateTexture(nil, "BACKGROUND")
tournerAtlas(fin, "ui-hud-actionbar-iconframe-flyoutbutton", 90, 47, 29)
fin:SetHeight(G.volantH)
fin:SetPoint("RIGHT", volant, "RIGHT", 0, 0)
local milieu = volant:CreateTexture(nil, "BACKGROUND")
atlas(milieu, "_ui-hud-actionbar-iconframe-flyoutmidleft", true)
milieu:SetHeight(G.volantH)
milieu:SetPoint("LEFT", debut, "RIGHT", 0, 0)
milieu:SetPoint("RIGHT", fin, "LEFT", 0, 0)
for _, t in ipairs({ debut, milieu, fin }) do
t:SetVertexColor(G.volantTeinte, G.volantTeinte, G.volantTeinte)
end
volant.boutons = {}
-- ferme par qui que ce soit (page, livre, categorie) : la fleche suit
volant:SetScript("OnHide", function()
local b = S.volantOuvert
S.volantOuvert = nil
if b then S.poserFleche(b) end
end)
S.volant = volant
return volant
end
-- ForeverUIVolant, appele par le bloc securise d'un groupe (CallMethod) :
-- les images des petits boutons et la fleche. Rien de protege.
function S.volantOuvertPar(k, vk)
local ancien = S.volantOuvert
local c = S.cases and S.cases[k]
local membres = S.volantsDonnees and S.volantsDonnees[vk]
if not c or not membres then return end
S.volantOuvert = c.bouton
if ancien and ancien ~= c.bouton then S.poserFleche(ancien) end
for i, p in ipairs(S.volant.boutons) do
p.sort = membres[i]
if p.sort then
p.icone:SetTexture(p.sort.icone)
majPetit(p)
end
end
S.poserFleche(c.bouton)
end
-- l'en-tete d'une categorie (SpellBookHeaderTemplate)
local function creerEntete(vue)
local h = CreateFrame("Frame", nil, vue)
h:SetWidth(G.vueL)
h:SetHeight(G.enteteH)
local fond = h:CreateTexture(nil, "BACKGROUND")
atlas(fond, "spellbook-list-backplate", true)
fond:SetWidth(416)
fond:SetHeight(106)
fond:SetPoint("LEFT", h, "LEFT", -85, 10)
fond:SetAlpha(0.65)
local texte = h:CreateFontString(nil, "ARTWORK")
police(texte, 24)
texte:SetJustifyH("LEFT")
texte:SetPoint("TOPLEFT", h, "TOPLEFT", -8, 0)
texte:SetPoint("BOTTOMRIGHT", h, "BOTTOMRIGHT", -60, 0)
local trait = h:CreateTexture(nil, "ARTWORK")
atlas(trait, "spellbook-divider", true)
trait:SetHeight(11)
trait:SetPoint("BOTTOMLEFT", h, "BOTTOMLEFT", -32, 0)
trait:SetPoint("BOTTOMRIGHT", h, "BOTTOMRIGHT", -60, 0)
h.texte = texte
h:Hide()
return h
end
-- ------------------------------------------------------------ le cadre
-- LA CROIX PAR-DESSUS LE LIVRE. Elle n'est pas sa fille : son niveau se
-- repose donc a chaque ouverture, au-dessus du metal (+20) et du titre (+21).
function S.leverCroix()
local fermer, livre = SpellBookCloseButton, S.livre
if not fermer or not livre then return end
fermer:SetFrameStrata(livre:GetFrameStrata())
fermer:SetFrameLevel(livre:GetFrameLevel() + 22)
end
local function boutonRouge(parent, normal, presse, gabarit)
local b = CreateFrame("Button", nil, parent, gabarit)
b:SetWidth(G.rougeCote)
b:SetHeight(G.rougeCote)
local e = ForeverUI.AtlasEntry(normal)
b:SetNormalTexture(e and e[1] or "")
atlas(b:GetNormalTexture(), normal, true)
b:SetPushedTexture(e and e[1] or "")
atlas(b:GetPushedTexture(), presse, true)
b:SetHighlightTexture(e and e[1] or "")
atlas(b:GetHighlightTexture(), "redbutton-highlight", true)
b:GetHighlightTexture():SetBlendMode("ADD")
return b
end
local function construireCadre(livre)
-- le fond de roche et le metal (PortraitFrameTemplate)
local roche = livre:CreateTexture(nil, "BACKGROUND")
roche:SetTexture(ROCHE, true)
if roche.SetHorizTile then roche:SetHorizTile(true) roche:SetVertTile(true) end
roche:SetPoint("TOPLEFT", livre, "TOPLEFT", 2, -21)
roche:SetPoint("BOTTOMRIGHT", livre, "BOTTOMRIGHT", -2, 2)
local metal = CreateFrame("Frame", nil, livre)
metal:SetAllPoints(livre)
metal:SetFrameLevel(livre:GetFrameLevel() + 20)
local p = {}
for _, coin in ipairs(METAL) do
local t = metal:CreateTexture(nil, "OVERLAY")
atlas(t, coin.nom)
t:SetPoint(coin.point, metal, coin.point, coin.x, coin.y)
p[coin.cle] = t
end
local function bord(nom, a1, c1, r1, a2, c2, r2)
local t = metal:CreateTexture(nil, "OVERLAY")
atlas(t, nom)
t:SetPoint(a1, c1, r1)
t:SetPoint(a2, c2, r2)
end
bord("_ui-frame-metal-edgetop", "TOPLEFT", p.hg, "TOPRIGHT", "TOPRIGHT", p.hd, "TOPLEFT")
bord("_ui-frame-metal-edgebottom", "BOTTOMLEFT", p.bg, "BOTTOMRIGHT", "BOTTOMRIGHT", p.bd, "BOTTOMLEFT")
bord("!ui-frame-metal-edgeleft", "TOPLEFT", p.hg, "BOTTOMLEFT", "BOTTOMLEFT", p.bg, "TOPLEFT")
bord("!ui-frame-metal-edgeright", "TOPRIGHT", p.hd, "BOTTOMRIGHT", "BOTTOMRIGHT", p.bd, "TOPRIGHT")
-- le portrait (PortraitContainer, sous le metal), rond
local cadrePortrait = CreateFrame("Frame", nil, livre)
cadrePortrait:SetAllPoints(livre)
cadrePortrait:SetFrameLevel(livre:GetFrameLevel() + 19)
local portrait = cadrePortrait:CreateTexture(nil, "OVERLAY")
portrait:SetWidth(G.portraitCote)
portrait:SetHeight(G.portraitCote)
portrait:SetPoint("TOPLEFT", livre, "TOPLEFT", G.portraitX, G.portraitY)
portrait:SetTexture(PORTRAIT)
livre.portrait = portrait
-- le titre (TitleContainer, au-dessus du metal)
local bandeau = CreateFrame("Frame", nil, livre)
bandeau:SetFrameLevel(livre:GetFrameLevel() + 21)
bandeau:SetHeight(G.titreH)
bandeau:SetPoint("TOPLEFT", livre, "TOPLEFT", G.titreX1, G.titreY)
bandeau:SetPoint("TOPRIGHT", livre, "TOPRIGHT", G.titreX2, G.titreY)
local titre = bandeau:CreateFontString(nil, "OVERLAY", "GameFontNormal")
titre:SetPoint("TOP", bandeau, "TOP", 0, -5)
titre:SetText(TEXTE.titre)
livre.titre = titre
-- la croix : celle de WotLK, rhabillee. Elle reste l'enfant de
-- SpellBookFrame : son OnClick ferme SON PARENT (HideUIPanel), et la
-- fermeture ne passe ainsi jamais par notre code.
local fermer = SpellBookCloseButton
if fermer then
S.leverCroix()
fermer:SetWidth(G.rougeCote)
fermer:SetHeight(G.rougeCote)
fermer:SetHitRectInsets(0, 0, 0, 0)
for _, etat in ipairs({ { "Normal", "redbutton-exit" }, { "Pushed", "redbutton-exit-pressed" },
{ "Highlight", "redbutton-highlight" } }) do
local e = ForeverUI.AtlasEntry(etat[2])
local t = fermer["Get" .. etat[1] .. "Texture"](fermer)
if not t and e then
fermer["Set" .. etat[1] .. "Texture"](fermer, e[1])
t = fermer["Get" .. etat[1] .. "Texture"](fermer)
end
if t then
atlas(t, etat[2], true)
t:ClearAllPoints()
t:SetAllPoints(fermer)
end
end
fermer:ClearAllPoints()
fermer:SetPoint("TOPRIGHT", livre, "TOPRIGHT", G.fermerX, G.fermerY)
end
-- agrandir / reduire (MaximizeMinimizeButtonFrameTemplate), a gauche
-- il dit au controleur de changer de mode (SecureHandlerClickTemplate) :
-- en combat aussi
local taille = boutonRouge(livre, "redbutton-condense", "redbutton-condense-pressed", "SecureHandlerClickTemplate")
taille:SetFrameLevel(livre:GetFrameLevel() + 22)
if fermer then
taille:SetPoint("RIGHT", fermer, "LEFT", 0, 0)
else
taille:SetPoint("TOPRIGHT", livre, "TOPRIGHT", G.fermerX - G.rougeCote, G.fermerY)
end
taille:SetAttribute("_onclick", [==[ self:GetFrameRef("ctrl"):SetAttribute("taille", 1) ]==])
taille:HookScript("OnClick", function() PlaySound("igMainMenuOptionCheckBoxOn") end)
livre.taille = taille
end
local function construire()
if S.livre or not SpellBookFrame then
return S.livre
end
-- le panneau de WotLK n'attrape plus la souris : il est vide
SpellBookFrame:EnableMouse(false)
local livre = CreateFrame("Frame", "ForeverUISpellBookFrame", SpellBookFrame)
livre:SetPoint("TOP", UIParent, "TOP", 0, G.haut)
livre:SetHeight(G.hauteur)
livre:SetWidth(G.largeur)
livre:EnableMouse(true)
-- PAS de SetToplevel ici : un livre qui se leve seul passerait par-dessus
-- la croix, sa soeur. SpellBookFrame, lui, est toplevel, et les leve
-- ensemble.
S.livre = livre
construireCadre(livre)
-- le livre ouvert : deux pages, ou une en reduit
local pages = CreateFrame("Frame", "ForeverUISpellBookPages", livre)
pages:SetPoint("BOTTOMLEFT", livre, "BOTTOMLEFT", 0, G.livreBas)
pages:SetHeight(G.livreH)
pages:SetWidth(G.livreL)
local gauche = pages:CreateTexture(nil, "BACKGROUND")
atlas(gauche, "spellbook-page-left-c60-2x", true)
gauche:SetPoint("TOPLEFT", pages, "TOPLEFT", 0, 0)
gauche:SetPoint("BOTTOMRIGHT", pages, "BOTTOM", 0, 0)
local droite = pages:CreateTexture(nil, "BACKGROUND")
atlas(droite, "spellbook-page-right-c60-2x", true)
droite:SetPoint("TOPLEFT", pages, "TOP", 0, 0)
droite:SetPoint("BOTTOMRIGHT", pages, "BOTTOMRIGHT", 0, 0)
local seule = pages:CreateTexture(nil, "BACKGROUND")
atlas(seule, "spellbook-page-right-c60-2x", true)
seule:SetAllPoints(pages)
seule:Hide()
pages.gauche, pages.droite, pages.seule = gauche, droite, seule
S.pages = pages
-- les onglets des categories
local onglets = CreateFrame("Frame", nil, pages)
onglets:SetPoint("TOPLEFT", pages, "TOPLEFT", G.ongletsX, G.ongletsY)
onglets:SetWidth(1)
onglets:SetHeight(G.ongletH)
onglets.boutons = {}
S.onglets = onglets
-- les deux vues ; le contenu tourne les pages a la roulette, par un bloc
-- securise (SecureHandlerMouseWheelTemplate)
local contenu = CreateFrame("Frame", "ForeverUISpellBookContent", pages, "SecureHandlerMouseWheelTemplate")
contenu:SetPoint("TOPLEFT", pages, "TOPLEFT", 0, G.pagesHaut)
contenu:SetPoint("BOTTOMRIGHT", pages, "BOTTOMRIGHT", 0, 0)
S.contenu = contenu
S.vues = {}
S.cases = {}
for v = 1, 2 do
local vue = CreateFrame("Frame", "ForeverUISpellBookView" .. v, contenu)
vue:SetWidth(G.vueL)
vue:SetHeight(G.vueH)
if v == 1 then
vue:SetPoint("TOPLEFT", contenu, "TOPLEFT", G.vue1X, G.vueY)
else
vue:SetPoint("TOPRIGHT", contenu, "TOPRIGHT", G.vue2X, G.vueY)
end
vue.cases = {}
for n = 1, G.casesParVue do
local k = (v - 1) * G.casesParVue + n
vue.cases[n] = creerCase(vue, k)
S.cases[k] = vue.cases[n]
end
vue.entete = creerEntete(vue)
S.vues[v] = vue
end
-- les pages : "Page n/m", precedente, suivante (de droite a gauche) ; un
-- clic dit au controleur de tourner (SecureHandlerClickTemplate)
local suivante = CreateFrame("Button", "ForeverUISpellBookNextPage", contenu, "SecureHandlerClickTemplate")
local precedente = CreateFrame("Button", "ForeverUISpellBookPrevPage", contenu, "SecureHandlerClickTemplate")
for _, f in ipairs({ { suivante, "NextPage", 1 }, { precedente, "PrevPage", -1 } }) do
local b = f[1]
b:SetWidth(G.flecheCote)
b:SetHeight(G.flecheCote)
b:SetNormalTexture(FLECHES .. f[2] .. "-Up")
b:SetPushedTexture(FLECHES .. f[2] .. "-Down")
b:SetDisabledTexture(FLECHES .. f[2] .. "-Disabled")
b:SetHighlightTexture(SURVOL_CARRE)
b:GetHighlightTexture():SetBlendMode("ADD")
b:SetAttribute("_onclick", ([[ self:GetFrameRef("ctrl"):SetAttribute("tourner", %d) ]]):format(f[3]))
b:HookScript("OnClick", function() PlaySound("igAbiliityPageTurn") end)
end
suivante:SetPoint("BOTTOMRIGHT", contenu, "BOTTOMRIGHT", G.pagerX, G.pagerY)
precedente:SetPoint("RIGHT", suivante, "LEFT", -G.pagerEcart, 0)
local texte = contenu:CreateFontString(nil, "ARTWORK")
police(texte, 14)
texte:SetPoint("RIGHT", precedente, "LEFT", -G.pagerEcart, 0)
S.pager = { suivante = suivante, precedente = precedente, texte = texte }
contenu:EnableMouseWheel(true)
contenu:SetAttribute("_onmousewheel", [[ self:GetFrameRef("ctrl"):SetAttribute("tourner", -delta) ]])
-- ce que WotLK montre dans SpellBookFrame, et que camelot n'a pas
-- les reglages (SpellBookSettingsDropdown) : TOPRIGHT (-30, -27) du livre
S.creerReglages(pages)
construireVolant(livre)
S.creerControleur(livre)
S.etoufferWotLK()
S.poserTaille()
return livre
end
S.construire = construire
-- ------------------------------------------------------------ les reglages
-- UIPanelArrowDropdownButtonTemplate : 15 x 16, la fleche
-- common-dropdown-a-button a sa taille d'atlas, centree ; au survol la meme
-- en ADD a 0,4 ; enfoncee, l'icone descend de (1, -1). Le menu s'ouvre
-- TOPLEFT sur BOTTOMLEFT (DropdownButton). Ses cases (SetupSettingsDropdown
-- de camelot) : masquer les passifs ; grouper en menus volants (retenu dans
-- ForeverUIDB, 3.3.5 n'a pas spellBookHideFlyouts ; groupe par defaut) ; tous
-- les rangs -- pour toutes les classes, a la demande (camelot l'ote au voleur
-- et au guerrier). Un clic coche et laisse le menu ouvert.
function S.creerReglages(pages)
local b = CreateFrame("Button", "ForeverUISpellBookSettingsButton", pages, "SecureHandlerClickTemplate")
b:SetWidth(G.reglagesL)
b:SetHeight(G.reglagesH)
b:SetPoint("TOPRIGHT", pages, "TOPRIGHT", G.reglagesX, G.reglagesY)
local icone = b:CreateTexture(nil, "ARTWORK")
atlas(icone, "common-dropdown-a-button")
icone:SetPoint("CENTER", b, "CENTER", 0, 0)
local survol = b:CreateTexture(nil, "HIGHLIGHT")
atlas(survol, "common-dropdown-a-button")
survol:SetPoint("CENTER", icone, "CENTER", 0, 0)
survol:SetBlendMode("ADD")
survol:SetAlpha(0.4)
b.icone = icone
b:SetScript("OnMouseDown", function() icone:SetPoint("CENTER", b, "CENTER", 1, -1) end)
b:SetScript("OnMouseUp", function() icone:SetPoint("CENTER", b, "CENTER", 0, 0) end)
-- la liste
local liste = CreateFrame("Frame", "ForeverUISpellBookSettingsList", pages)
liste:SetFrameStrata("DIALOG")
liste:EnableMouse(true)
liste:SetPoint("TOPLEFT", b, "BOTTOMLEFT", 0, 0)
liste:Hide()
local tranches = ForeverUI.CreateNineSlice(liste, "common-dropdown-bg-c60", MENU.fondCoin, MENU.fondMarges, "BACKGROUND")
for _, t in ipairs(tranches or {}) do t:SetAlpha(MENU.fondAlpha) end
liste.lignes = {}
local plusLong = 0
for i, r in ipairs(REGLAGES) do
local l = CreateFrame("Button", "ForeverUISpellBookSettingsEntry" .. i, liste, "SecureHandlerClickTemplate")
l:SetID(r.bit)
l:SetHeight(MENU.ligneH)
l:SetPoint("TOPLEFT", liste, "TOPLEFT", MENU.ligneX, -MENU.bord - (i - 1) * MENU.ligneH)
l:SetHighlightTexture("Interface" .. SEP .. "QuestFrame" .. SEP .. "UI-QuestTitleHighlight")
l:GetHighlightTexture():SetBlendMode("ADD")
local texte = l:CreateFontString(nil, "ARTWORK", "GameFontHighlightLeft")
texte:SetPoint("LEFT", l, "LEFT", MENU.texteX, 0)
texte:SetText(r.texte)
plusLong = math.max(plusLong, texte:GetStringWidth() or 0)
local case = l:CreateTexture(nil, "BORDER")
ForeverUI.SetAtlas(case, "common-dropdown-ticksquare", true)
case:SetWidth(MENU.case)
case:SetHeight(MENU.case)
case:SetPoint("LEFT", l, "LEFT", 0, 0)
local coche = l:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(coche, "common-dropdown-icon-checkmark-yellow", true)
coche:SetWidth(MENU.cocheL)
coche:SetHeight(MENU.cocheH)
coche:SetPoint("CENTER", case, "CENTER", MENU.cocheX, MENU.cocheY)
coche:Hide()
l.texte, l.coche, l.reglage = texte, coche, r
-- UIDropDownMenuButton_OnClick, keepShownOnClick : la liste reste
-- ouverte ; le reglage passe par le controleur, en combat aussi
l:SetAttribute("_onclick", [==[ self:GetFrameRef("ctrl"):SetAttribute("reglage", self:GetID()) ]==])
l:HookScript("OnClick", function() PlaySound("UChatScrollButton") end)
liste.lignes[i] = l
end
local largeur = plusLong + MENU.largeurPlus + MENU.marge
liste:SetWidth(largeur)
liste:SetHeight(#REGLAGES * MENU.ligneH + 2 * MENU.bord)
for _, l in ipairs(liste.lignes) do l:SetWidth(largeur - MENU.marge) end
-- la fleche ouvre ou ferme la liste, en combat aussi ; la liste se ferme
-- d'elle-meme 2 s apres que la souris l'a quittee
b:SetFrameRef("liste", liste)
b:SetAttribute("_onclick", ([==[
local l = self:GetFrameRef("liste")
if l:IsShown() then
l:Hide()
else
l:Show()
l:RegisterAutoHide(%d)
l:AddToAutoHide(self)
end
]==]):format(MENU.attente))
S.reglages, S.listeReglages = b, liste
return b
end
-- en combat aussi : les coches suivent la combinaison (textures seulement)
function S.cocherReglages(reg)
if not S.listeReglages then return end
for _, l in ipairs(S.listeReglages.lignes) do
local actif = math.floor(reg / l.reglage.bit) % 2 == 1
if l.reglage.inverse then actif = not actif end
if actif then l.coche:Show() else l.coche:Hide() end
end
end
function S.etoufferWotLK()
for _, region in ipairs({ SpellBookFrame:GetRegions() }) do
etouffer(region)
end
for _, enfant in ipairs({ SpellBookFrame:GetChildren() }) do
if enfant ~= S.livre and enfant ~= SpellBookCloseButton then
etouffer(enfant)
end
end
end
-- deux pages ou une : les images de la page et du bouton. Les largeurs et la
-- vue de droite sont au bloc securise "poser" ; ceci suit, en combat aussi.
function S.poserTaille(reduit)
local livre, pages = S.livre, S.pages
if not livre then return end
if reduit == nil then reduit = reglages().reduit end
if reduit then
pages.gauche:Hide() pages.droite:Hide() pages.seule:Show()
atlas(livre.taille:GetNormalTexture(), "redbutton-expand", true)
atlas(livre.taille:GetPushedTexture(), "redbutton-expand-pressed", true)
else
pages.gauche:Show() pages.droite:Show() pages.seule:Hide()
atlas(livre.taille:GetNormalTexture(), "redbutton-condense", true)
atlas(livre.taille:GetPushedTexture(), "redbutton-condense-pressed", true)
end
end
-- ------------------------------------------------------------ les onglets
-- un onglet dit au controleur "categorie n" (SecureHandlerClickTemplate) ;
-- il se cree hors combat seulement
local function creerOnglet(parent, n)
local b = CreateFrame("Button", "ForeverUISpellBookTab" .. n, parent, "SecureHandlerClickTemplate")
b:SetID(n)
b:SetFrameRef("ctrl", S.ctrl)
b:SetAttribute("_onclick", [[ self:GetFrameRef("ctrl"):SetAttribute("categorie", self:GetID()) ]])
S.ctrl:SetFrameRef("o" .. n, b)
S.ctrl:Execute(("ONGLETS[%d] = self:GetFrameRef(\"o%d\")"):format(n, n))
b:SetWidth(G.ongletL)
b:SetHeight(G.ongletH)
local icone = b:CreateTexture(nil, "ARTWORK")
icone:SetWidth(G.iconeOngletL)
icone:SetHeight(G.iconeOngletH)
icone:SetPoint("CENTER", b, "CENTER", -1, -1)
-- le cadre PAR-DESSUS l'icone (ARTWORK sous-niveau 1) : un cadre fils
local dessus = CreateFrame("Frame", nil, b)
dessus:SetAllPoints(b)
dessus:SetFrameLevel(b:GetFrameLevel() + 1)
local cadre = dessus:CreateTexture(nil, "ARTWORK")
atlas(cadre, "spellbook-tab-frame-c60")
cadre:SetPoint("BOTTOM", b, "BOTTOM", 0, 1)
local actif = dessus:CreateTexture(nil, "ARTWORK")
atlas(actif, "spellbook-tab-frame-glow-c60")
actif:SetPoint("BOTTOM", b, "BOTTOM", 0, 1)
local lueur = dessus:CreateTexture(nil, "OVERLAY")
atlas(lueur, "spellbook-tab-frame-glow-gradient-c60")
lueur:SetPoint("BOTTOM", b, "BOTTOM", 0, 0)
b.icone, b.cadre, b.actif, b.lueur = icone, cadre, actif, lueur
b:HookScript("OnClick", function() PlaySound("igSpellBookOpen") end)
b:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT", -12, -6)
GameTooltip:SetText(self.nom)
GameTooltip:Show()
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
return b
end
-- hors combat : un onglet par categorie, a sa place
local function poserOnglets(cats)
local onglets = S.onglets
for i, cat in ipairs(cats) do
local b = onglets.boutons[i]
if not b then
b = creerOnglet(onglets, i)
onglets.boutons[i] = b
end
b.index, b.nom = i, cat.nom
b.icone:SetTexture(cat.icone)
b:ClearAllPoints()
b:SetPoint("TOPLEFT", onglets, "TOPLEFT", (i - 1) * (G.ongletL + G.ongletEcart), 0)
b:Show()
end
for i = #cats + 1, #onglets.boutons do
onglets.boutons[i]:Hide()
end
end
-- en combat aussi : l'onglet choisi s'allume (textures seulement ; son
-- activation est au bloc securise)
local function allumerOnglets(choisie)
for i, b in ipairs(S.onglets.boutons) do
-- 3.3.5 n'a pas SetShown
if i == choisie then
b.cadre:Hide() b.actif:Show() b.lueur:Show()
else
b.cadre:Show() b.actif:Hide() b.lueur:Hide()
end
end
end
-- ------------------------------------------------------------ le remplissage
S.etat = { categorie = 1, page = 1 }
-- Ce que l'on demande au controleur, hors combat seulement (le reste passe
-- par les onglets, les fleches et la roulette, qui sont securises).
function S.choisir(index)
if InCombatLockdown() or not S.ctrl then return end
S.ctrl:SetAttribute("categorie", index)
end
function S.tourner(sens)
if InCombatLockdown() or not S.ctrl then return end
S.ctrl:SetAttribute("tourner", sens)
end
-- LE CONTROLEUR. Son environnement securise garde les donnees publiees (D :
-- les cases de chaque page, NP : les pages de chaque categorie, V : les
-- sorts de chaque groupe), l'etat (CAT, PAGE, OUVERT) et les poignees des
-- cadres. Tout bloc est ecrit sans accolade ni le mot interdit.
local AFFICHER = [==[
local liste = D[REG .. ":" .. MODE .. ":" .. CAT .. ":" .. PAGE]
VOL:Hide()
OUVERT = nil
for k = 1, NB do
CASES[k]:Hide()
BOUTONS[k]:SetAttribute("volant", nil)
end
if liste then
for i = 1, #liste do
local e = liste[i]
local c = CASES[e[1]]
local b = BOUTONS[e[1]]
c:ClearAllPoints()
c:SetPoint("TOPLEFT", "$parent", "TOPLEFT", e[2], e[3])
if e[4] == "" then b:SetAttribute("type1", nil) else b:SetAttribute("type1", e[4]) end
if e[5] == "" then b:SetAttribute("spell", nil) else b:SetAttribute("spell", e[5]) end
if e[6] == "" then
b:SetAttribute("type2", nil)
b:SetAttribute("macrotext2", nil)
else
b:SetAttribute("type2", e[6])
b:SetAttribute("macrotext2", e[7])
end
if e[8] > 0 then b:SetAttribute("volant", e[8]) end
c:Show()
end
end
for i = 1, NC do
if i == CAT then ONGLETS[i]:Disable() else ONGLETS[i]:Enable() end
end
if PAGE > 1 then PREC:Enable() else PREC:Disable() end
if PAGE < NP[REG .. ":" .. MODE .. ":" .. CAT] then SUIV:Enable() else SUIV:Disable() end
control:CallMethod("ForeverUIVisuels", CAT, PAGE, MODE, REG)
]==]
local CHANGEMENT = [==[
if name == "categorie" and value then
self:SetAttribute("categorie", nil)
if value >= 1 and value <= NC then
CAT = value
PAGE = 1
control:RunAttribute("afficher")
end
elseif name == "tourner" and value then
self:SetAttribute("tourner", nil)
local p = PAGE + value
if p >= 1 and p <= NP[REG .. ":" .. MODE .. ":" .. CAT] then
PAGE = p
control:RunAttribute("afficher")
end
elseif name == "taille" and value then
self:SetAttribute("taille", nil)
if MODE == 2 then
MODE = 1
PAGE = PAGE * 2 - 1
else
MODE = 2
PAGE = ceil(PAGE / 2)
end
if PAGE > NP[REG .. ":" .. MODE .. ":" .. CAT] then PAGE = NP[REG .. ":" .. MODE .. ":" .. CAT] end
control:RunAttribute("poser")
control:RunAttribute("afficher")
elseif name == "reglage" and value then
self:SetAttribute("reglage", nil)
if floor(REG / value) % 2 == 1 then REG = REG - value else REG = REG + value end
if PAGE > NP[REG .. ":" .. MODE .. ":" .. CAT] then PAGE = NP[REG .. ":" .. MODE .. ":" .. CAT] end
control:RunAttribute("afficher")
end
]==]
-- MaximizeMinimizeButtonFrameTemplate : 1618 / 809 de large, deux pages ou
-- une (la vue de droite). %d : les largeurs de G.
local POSER = [==[
if MODE == 1 then
LIVRE:SetWidth(%d)
PAGES:SetWidth(%d)
VUE2:Hide()
else
LIVRE:SetWidth(%d)
PAGES:SetWidth(%d)
VUE2:Show()
end
]==]
-- SpellFlyoutMixin:Toggle, enveloppe autour du OnClick de chaque case : le
-- meme groupe ferme le menu, un autre le deplace. Un modificateur ne l'ouvre
-- pas (le lien). %s : la largeur, calculee ici.
local OUVRIR = [==[
local v = self:GetAttribute("volant")
if v and not IsModifierKeyDown() then
if OUVERT == self and VOL:IsShown() then
VOL:Hide()
OUVERT = nil
else
local liste = V[v]
local n = #liste
for i = 1, NBPETITS do
if i <= n then
PETITS[i]:SetAttribute("spell", liste[i])
PETITS[i]:Show()
else
PETITS[i]:Hide()
end
end
VOL:SetWidth(%d + n * %d)
VOL:ClearAllPoints()
VOL:SetPoint("LEFT", self, "RIGHT", %d, 0)
VOL:Show()
OUVERT = self
control:CallMethod("ForeverUIVolant", self:GetID(), v)
end
end
]==]
-- un petit bouton : le sort part, puis le menu se ferme (sauf le lien)
local PETIT_AVANT = [==[ if not IsModifiedClick("CHATLINK") then return nil, "fermer" end ]==]
local PETIT_APRES = [==[ VOL:Hide() OUVERT = nil ]==]
-- le livre se ferme : le menu aussi (FlyoutButtonMixin:OnHide)
local CACHE = [==[ VOL:Hide() OUVERT = nil ]==]
function S.creerControleur(livre)
local ctrl = CreateFrame("Frame", "ForeverUISpellBookControl", livre, "SecureHandlerAttributeTemplate")
S.ctrl = ctrl
for k, c in ipairs(S.cases) do
ctrl:SetFrameRef("c" .. k, c)
ctrl:SetFrameRef("b" .. k, c.bouton)
ctrl:WrapScript(c.bouton, "OnClick", OUVRIR:format(G.volantDebut + G.volantFin - G.volantEcart,
G.petitCote + G.volantEcart, G.volantDecalage))
end
ctrl:SetFrameRef("vol", S.volant)
ctrl:SetFrameRef("livre", livre)
ctrl:SetFrameRef("pages", S.pages)
ctrl:SetFrameRef("vue2", S.vues[2])
livre.taille:SetFrameRef("ctrl", ctrl)
for _, l in ipairs(S.listeReglages.lignes) do l:SetFrameRef("ctrl", ctrl) end
ctrl:SetFrameRef("prec", S.pager.precedente)
ctrl:SetFrameRef("suiv", S.pager.suivante)
for _, b in ipairs({ S.pager.precedente, S.pager.suivante, S.contenu }) do
b:SetFrameRef("ctrl", ctrl)
end
ctrl:Execute(([==[
NB = %d
CASES = newtable()
BOUTONS = newtable()
for k = 1, NB do
CASES[k] = self:GetFrameRef("c" .. k)
BOUTONS[k] = self:GetFrameRef("b" .. k)
end
VOL = self:GetFrameRef("vol")
LIVRE = self:GetFrameRef("livre")
PAGES = self:GetFrameRef("pages")
VUE2 = self:GetFrameRef("vue2")
MODE = 2
REG = 0
PREC = self:GetFrameRef("prec")
SUIV = self:GetFrameRef("suiv")
ONGLETS = newtable()
PETITS = newtable()
NBPETITS = 0
D = newtable()
NP = newtable()
V = newtable()
NC = 0
CAT = 1
PAGE = 1
]==]):format(#S.cases))
ctrl:SetAttribute("afficher", AFFICHER)
ctrl:SetAttribute("poser", POSER:format(G.largeurReduite, G.livreLReduit, G.largeur, G.livreL))
ctrl:SetAttribute("_onattributechanged", CHANGEMENT)
ctrl:WrapScript(S.contenu, "OnHide", CACHE)
-- rendus par CallMethod : du code ordinaire, permis en combat
ctrl.ForeverUIVisuels = function(_, cat, page, mode, reg) S.visuels(cat, page, mode, reg) end
ctrl.ForeverUIVolant = function(_, k, vk) S.volantOuvertPar(k, vk) end
return ctrl
end
-- les petits boutons du menu volant : autant que le plus grand groupe, crees
-- hors combat, chacun connu du controleur
local function assezDePetits(n)
local volant, ctrl = S.volant, S.ctrl
for i = #volant.boutons + 1, n do
local p = creerPetit(volant, i)
volant.boutons[i] = p
ctrl:SetFrameRef("p" .. i, p)
ctrl:WrapScript(p, "OnClick", PETIT_AVANT, PETIT_APRES)
ctrl:Execute(("PETITS[%d] = self:GetFrameRef(\"p%d\") NBPETITS = %d"):format(i, i, i))
end
end
-- une chaine pour le bloc securise
local function q(texte)
return string.format("%q", texte or "")
end
-- LE CALCUL, HORS COMBAT : chaque categorie mise en page, publiee au
-- controleur, puis affichee a l'etat voulu.
function S.maj()
local livre = S.livre
if not livre then return end
if InCombatLockdown() then
S.enAttente = true
return
end
S.enAttente, S.sale = nil, nil
local cats = categories()
local e = S.etat
if e.categorie > #cats then e.categorie = 1 end
poserOnglets(cats)
local mode = reglages().reduit and 1 or 2
local courantes = optionsCourantes()
local reg = (courantes.passifs and 1 or 0) + (courantes.volants and 2 or 0) + (courantes.rangs and 4 or 0)
-- TOUT EST PUBLIE : les huit jeux de reglages (REG, trois bits), chacun
-- en une page (1) ou deux (2), pour que les reglages et le bouton
-- agrandir / reduire marchent en combat. Dans un jeu, les vues sont les
-- memes pour les deux modes ; seul leur regroupement en pages change.
S.pagesDonnees, S.np, S.volantsDonnees = {}, {}, {}
local code = { "wipe(D) wipe(NP) wipe(V)" }
local plusGrand = 0
for r = 0, 7 do
local opts = { passifs = r % 2 == 1, volants = math.floor(r / 2) % 2 == 1, rangs = math.floor(r / 4) % 2 == 1 }
S.pagesDonnees[r], S.np[r] = { {}, {} }, { {}, {} }
for ci, cat in ipairs(cats) do
local vues = mettreEnPage(cat.nom, listeAffichee(cats, ci, opts))
for parPage = 1, 2 do
local nPages = math.max(1, math.ceil(#vues / parPage))
S.np[r][parPage][ci] = nPages
S.pagesDonnees[r][parPage][ci] = {}
table.insert(code, ('NP["%d:%d:%d"] = %d'):format(r, parPage, ci, nPages))
for page = 1, nPages do
local donnees = { entetes = {}, cases = {} }
S.pagesDonnees[r][parPage][ci][page] = donnees
local entrees = {}
for slot = 1, parPage do
local n = 0
for _, el in ipairs(vues[(page - 1) * parPage + slot] or {}) do
if el.entete then
donnees.entetes[slot] = el
else
n = n + 1
local k = (slot - 1) * G.casesParVue + n
local sort = el.sort
donnees.cases[k] = sort
-- un groupe n'est publie qu'une fois, pour les deux modes
if sort.volant and not sort.vk then
table.insert(S.volantsDonnees, sort.membres)
sort.vk = #S.volantsDonnees
local textes = {}
for _, m in ipairs(sort.membres) do table.insert(textes, q(S.texteDuSort(m))) end
table.insert(code, ("V[%d] = newtable(%s)"):format(sort.vk, table.concat(textes, ", ")))
plusGrand = math.max(plusGrand, #sort.membres)
end
local lance = not (sort.passif or sort.volant)
table.insert(entrees, ("newtable(%d, %.14g, %d, %s, %s, %s, %s, %d)"):format(k,
(el.colonne - 1) * (G.celluleL + G.ecartX), -el.y,
q(lance and "spell" or ""), q(lance and S.texteDuSort(sort) or ""),
q(sort.familier and "macro" or ""), q(sort.familier and ("/petautocasttoggle " .. sort.nom) or ""),
sort.vk or 0))
end
end
end
table.insert(code, ('D["%d:%d:%d:%d"] = newtable(%s)'):format(r, parPage, ci, page, table.concat(entrees, ", ")))
end
end
end
end
assezDePetits(plusGrand)
if e.page > S.np[reg][mode][e.categorie] then e.page = S.np[reg][mode][e.categorie] end
-- "poser" A CHAQUE CALCUL : la mise en page suit le reglage, meme quand il
-- n'arrive qu'apres elle (les variables sauvegardees se chargent apres le
-- fichier, donc apres la construction du livre)
table.insert(code, ("NC = %d CAT = %d PAGE = %d MODE = %d REG = %d control:RunAttribute(\"poser\") control:RunAttribute(\"afficher\")")
:format(#cats, e.categorie, e.page, mode, reg))
S.ctrl:Execute(table.concat(code, "\n"))
end
-- ForeverUIVisuels : ce que le bloc securise vient de poser, en images et en
-- textes. Rien de protege ici : c'est ce qui rend le livre lisible en combat.
function S.visuels(cat, page, mode, reg)
local e = S.etat
e.categorie, e.page = cat, page
mode, reg = mode or 2, reg or 0
-- le mode et les reglages choisis se retiennent, meme changes en combat
-- (SetCVar n'est pas protege)
reglages().reduit = (mode == 1) or nil
reglages().masquerPassifs = (reg % 2 == 1) or nil
reglages().sansVolants = (math.floor(reg / 2) % 2 == 1) or nil
local rangs = math.floor(reg / 4) % 2 == 1
if rangs ~= (GetCVar("ShowAllSpellRanks") == "1") then
SetCVar("ShowAllSpellRanks", rangs and "1" or "0")
end
S.reg = reg
S.cocherReglages(reg)
S.poserTaille(mode == 1)
e.pages = S.np and S.np[reg][mode][cat] or 1
allumerOnglets(cat)
local donnees = S.pagesDonnees and S.pagesDonnees[reg][mode][cat] and S.pagesDonnees[reg][mode][cat][page]
for slot, vue in ipairs(S.vues) do
local el = donnees and donnees.entetes[slot]
if el then
vue.entete.texte:SetText(el.entete)
vue.entete:ClearAllPoints()
vue.entete:SetPoint("TOPLEFT", vue, "TOPLEFT", 0, -el.y)
vue.entete:Show()
else
vue.entete:Hide()
end
end
for k, c in ipairs(S.cases) do
local sort = donnees and donnees.cases[k]
c.sort = sort
if sort then remplirCase(c, sort) end
end
S.pager.texte:SetText(string.format(TEXTE.page, e.page, e.pages))
end
-- l'etat des cases montrees : recharges, lancement automatique
function S.majEtats()
if not S.vues then return end
if S.volant and S.volant:IsShown() then
for _, p in ipairs(S.volant.boutons) do
if p:IsShown() then majPetit(p) end
end
end
for _, vue in ipairs(S.vues) do
for _, c in ipairs(vue.cases) do
if c:IsShown() and c.sort then S.majEtat(c) end
end
end
end
-- ------------------------------------------------------------ l'assemblage
construire()
if S.livre then
-- a chaque ouverture : le livre demande (sorts ou familier) et son contenu
SpellBookFrame:HookScript("OnShow", function()
S.etoufferWotLK()
S.leverCroix()
-- en combat, le livre s'ouvre tel qu'il a ete publie
if InCombatLockdown() then return end
if SpellBookFrame.bookType == BOOKTYPE_PET then
local cats = categories()
for i, cat in ipairs(cats) do
if cat.familier then S.etat.categorie = i S.etat.page = 1 end
end
elseif S.etat.categorie and categories()[S.etat.categorie] and categories()[S.etat.categorie].familier then
S.etat.categorie = 1
S.etat.page = 1
end
S.maj()
end)
if hooksecurefunc and SpellBookFrame_Update then
hooksecurefunc("SpellBookFrame_Update", S.etoufferWotLK)
end
-- LE LIVRE DOIT ETRE PUBLIE AVANT LE COMBAT, ouvert ou non : on peut
-- l'ouvrir en combat. Un changement le marque, l'image suivante le
-- recalcule (SPELLS_CHANGED arrive en rafales) ; PLAYER_REGEN_DISABLED,
-- dernier moment hors verrou, rattrape ce qui n'a pas eu son image.
local veille = CreateFrame("Frame")
for _, ev in ipairs({ "PLAYER_ENTERING_WORLD", "SPELLS_CHANGED", "LEARNED_SPELL_IN_TAB", "PET_BAR_UPDATE",
"UNIT_PET", "SPELL_UPDATE_COOLDOWN", "CURRENT_SPELL_CAST_CHANGED", "PLAYER_REGEN_DISABLED",
"PLAYER_REGEN_ENABLED", "CVAR_UPDATE" }) do
veille:RegisterEvent(ev)
end
local function aLImageSuivante()
veille:SetScript("OnUpdate", nil)
if S.sale and not InCombatLockdown() then S.maj() end
end
function S.marquer()
S.sale = true
veille:SetScript("OnUpdate", aLImageSuivante)
end
veille:SetScript("OnEvent", function(_, ev)
if ev == "SPELL_UPDATE_COOLDOWN" or ev == "CURRENT_SPELL_CAST_CHANGED" then
S.majEtats()
return
end
if ev == "PET_BAR_UPDATE" then S.majEtats() end
if ev == "PLAYER_REGEN_DISABLED" then
if S.sale then S.maj() end
return
end
if ev == "PLAYER_REGEN_ENABLED" then
if S.sale or S.enAttente then S.maj() end
return
end
S.marquer()
end)
end
ForeverUI.SpellBookDebug = function()
local prefixe = "|cff66ccffForeverUI|r "
if not S.livre then
DEFAULT_CHAT_FRAME:AddMessage(prefixe .. "grimoire : pas construit.")
return
end
local cats = categories()
local e = S.etat
local noms = {}
for _, c in ipairs(cats) do table.insert(noms, c.nom .. " (" .. c.nombre .. ")") end
DEFAULT_CHAT_FRAME:AddMessage(prefixe .. string.format(
"grimoire : %.0f x %.0f, %s | categorie %d/%d, page %d/%d | %s",
S.livre:GetWidth(), S.livre:GetHeight(), reglages().reduit and "une page" or "deux pages",
e.categorie, #cats, e.page, e.pages or 1, table.concat(noms, ", ")))
end