cc7d2d45fa
- Talents : trois arbres cote a cote facon camelot sur PlayerTalentFrame, noeuds carres/ronds (TalentsData.lua genere des DBC), fleches, portes, changements en attente (Apply / Undo), onglets lateraux des specialisations, Activate, talents du familier en fenetre reduite, recherche (marques sur les noeuds, apercu, options, absents des barres), confirmation a la fermeture, glisser un sort de talent appris vers une barre - Glyphes : page refaite sur l'art fourni (tools/cuire_glyphes.py), lueurs, etincelles, cercles concentriques tournants ; le GlyphFrame de WotLK reste sous PlayerTalentFrame (le reparenter plantait le client a la deconnexion) - Cadre du familier facon camelot (PetFrame.lua) - Superposition.lua : feuille, grimoire et talents passent entierement l'un devant l'autre, grimoire et talents deplacables - Grimoire : la recherche "Missing from action bar" suit les barres Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1836 lines
71 KiB
Lua
1836 lines
71 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, espace = 20,
|
|
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
|
|
passifsDesactives = "Hiding Passives is disabled while searching", -- SPELLBOOK_SEARCH_HIDE_PASSIVES_DISABLED
|
|
}
|
|
|
|
-- 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
|
|
S.G = G
|
|
S.police = police
|
|
|
|
-- 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
|
|
S.optionsCourantes = optionsCourantes
|
|
|
|
-- ------------------------------------------------------------ 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 }.
|
|
--
|
|
-- PLUSIEURS SECTIONS (les resultats d'une recherche) : chacune garde ses
|
|
-- rangees ; une section qui commence sur une vue deja remplie est precedee
|
|
-- de l'espaceur de camelot (spacerSize 20 + yPadding 10), et passe a la vue
|
|
-- suivante s'il n'y a plus la place de son en-tete et d'une rangee.
|
|
local function mettreEnPageGroupes(groupes)
|
|
local vues = {}
|
|
local vue, occupe
|
|
local pas = G.sortH + G.ecartY
|
|
local function nouvelleVue(titre)
|
|
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
|
|
for _, g in ipairs(groupes) do
|
|
local titre, sorts = g.titre, g.sorts
|
|
if not vue then
|
|
nouvelleVue(titre)
|
|
elseif occupe + G.espace + G.ecartY + G.enteteH + G.ecartY + pas > G.vueH then
|
|
nouvelleVue(titre)
|
|
else
|
|
occupe = occupe + G.espace + G.ecartY
|
|
if titre then
|
|
table.insert(vue, { entete = titre, colonne = 1, y = occupe })
|
|
occupe = occupe + G.enteteH + G.ecartY
|
|
end
|
|
end
|
|
local i = 1
|
|
while i <= #sorts do
|
|
local dispo = math.floor((G.vueH - occupe) / pas)
|
|
if dispo < 1 then
|
|
nouvelleVue(titre)
|
|
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(titre)
|
|
end
|
|
end
|
|
end
|
|
if not vue then nouvelleVue(nil) end
|
|
return vues
|
|
end
|
|
S.mettreEnPageGroupes = mettreEnPageGroupes
|
|
|
|
local function mettreEnPage(titre, sorts)
|
|
return mettreEnPageGroupes({ { titre = titre, sorts = sorts } })
|
|
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
|
|
livre.bandeau = bandeau
|
|
|
|
-- 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.entetes = { creerEntete(vue) }
|
|
vue.entete = vue.entetes[1]
|
|
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)
|
|
-- SetupHidePassivesCheckbox : desactivee en recherche, avec son
|
|
-- infobulle (les deux autres n'en ont pas)
|
|
if r.bit == 1 then
|
|
l:SetScript("OnEnter", function(self)
|
|
if not S.reglagesGrises then return end
|
|
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
|
GameTooltip:SetText(TEXTE.passifsDesactives, 1, 1, 1, 1, 1)
|
|
GameTooltip:Show()
|
|
end)
|
|
l:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
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 : grise pendant une recherche (le bloc securise
|
|
-- desactive les lignes ; ici, la couleur du texte)
|
|
function S.griserReglages(grise)
|
|
if not S.listeReglages then return end
|
|
S.reglagesGrises = grise
|
|
for _, l in ipairs(S.listeReglages.lignes) do
|
|
-- "Show all spell ranks" reste actif pendant une recherche (demande
|
|
-- du 2026-09-25 ; camelot le desactive)
|
|
if grise and l.reglage.bit ~= 4 then
|
|
l.texte:SetTextColor(0.5, 0.5, 0.5)
|
|
else
|
|
l.texte:SetTextColor(1, 1, 1)
|
|
end
|
|
end
|
|
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 S.surTaille then S.surTaille(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
|
|
for i = 1, NE do
|
|
if CAT == 0 and ENTREES[i]:GetID() ~= 4 then ENTREES[i]:Disable() else ENTREES[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) or (value == 0 and NP[REG .. ":" .. MODE .. ":0"]) 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 == "effacer" and value then
|
|
self:SetAttribute("effacer", nil)
|
|
if CAT == 0 then
|
|
CAT = 1
|
|
PAGE = 1
|
|
control:RunAttribute("afficher")
|
|
end
|
|
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 i, l in ipairs(S.listeReglages.lignes) do
|
|
l:SetFrameRef("ctrl", ctrl)
|
|
ctrl:SetFrameRef("e" .. i, l)
|
|
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()
|
|
ENTREES = newtable()
|
|
NE = %d
|
|
for i = 1, NE do ENTREES[i] = self:GetFrameRef("e" .. i) end
|
|
NBPETITS = 0
|
|
D = newtable()
|
|
NP = newtable()
|
|
V = newtable()
|
|
NC = 0
|
|
CAT = 1
|
|
PAGE = 1
|
|
]==]):format(#S.cases, #S.listeReglages.lignes))
|
|
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
|
|
-- LA RECHERCHE (SpellBookSearch.lua) : ses resultats forment une
|
|
-- categorie de plus, la 0, sans onglet. Sans resultat, on en sort
|
|
-- (DisplayFullSearchResults -> ClearActiveSearchState).
|
|
local recherche = ForeverUI.SpellBookSearch
|
|
if recherche then recherche.nouveauCalcul() end
|
|
local groupesRecherche = {}
|
|
if recherche and recherche.active() then
|
|
-- une variante par jeu de reglages : la recherche suit "Hide Passives"
|
|
-- et "Show all spell ranks", et ne groupe JAMAIS (demandes du
|
|
-- 2026-09-25 ; camelot y montre passifs et tous les rangs, et groupe)
|
|
local vide = true
|
|
for r = 0, 7 do
|
|
local g = recherche.groupes(cats, { passifs = r % 2 == 1, volants = true,
|
|
rangs = math.floor(r / 4) % 2 == 1 })
|
|
groupesRecherche[r] = g
|
|
if #g > 0 then vide = false end
|
|
end
|
|
if vide then
|
|
recherche.quitter()
|
|
groupesRecherche = {}
|
|
end
|
|
end
|
|
if e.categorie == 0 and not groupesRecherche[0] 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] = { {}, {} }, { {}, {} }
|
|
-- la categorie 0 : les resultats de la recherche, pour ce jeu de
|
|
-- reglages
|
|
local g = groupesRecherche[r]
|
|
for ci = (g and 0 or 1), #cats do
|
|
local cat = cats[ci]
|
|
local vues
|
|
if ci == 0 then
|
|
vues = mettreEnPageGroupes(g)
|
|
else
|
|
vues = mettreEnPage(cat.nom, listeAffichee(cats, ci, opts))
|
|
end
|
|
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] = donnees.entetes[slot] or {}
|
|
table.insert(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]
|
|
-- les en-tetes : un par section de la vue (cadres ordinaires, crees au
|
|
-- besoin -- permis en combat)
|
|
for slot, vue in ipairs(S.vues) do
|
|
local liste = donnees and donnees.entetes[slot] or {}
|
|
for i, el in ipairs(liste) do
|
|
local h = vue.entetes[i]
|
|
if not h then
|
|
h = creerEntete(vue)
|
|
vue.entetes[i] = h
|
|
end
|
|
h.texte:SetText(el.entete)
|
|
h:ClearAllPoints()
|
|
h:SetPoint("TOPLEFT", vue, "TOPLEFT", 0, -el.y)
|
|
h:Show()
|
|
end
|
|
for i = #liste + 1, #vue.entetes do vue.entetes[i]:Hide() end
|
|
end
|
|
-- les reglages sont grises pendant une recherche (camelot les desactive)
|
|
S.griserReglages(cat == 0)
|
|
if S.surVisuels then S.surVisuels(cat) 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
|
|
-- deplacable par son titre ; devant quand il s'ouvre ou qu'on le clique
|
|
ForeverUI.Superposition.deplacable(S.livre, S.livre.bandeau, "grimoire")
|
|
ForeverUI.Superposition.inscrire("grimoire", SpellBookFrame, function()
|
|
return { S.livre, _G.ForeverUISpellFlyout, _G.ForeverUISpellBookSettingsList,
|
|
_G.ForeverUISpellBookSearchPreview }
|
|
end)
|
|
-- 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
|