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>
802 lines
30 KiB
Lua
802 lines
30 KiB
Lua
-- ForeverUI : la recherche des talents de camelot (docs/TALENTS.md, etape 4).
|
|
--
|
|
-- RELEVE -- blizzard_playerspells (camelot/classtalents), blizzard_sharedtalentui
|
|
-- (classtalentsearch, talentbuttonart, sharedtalentbuttontemplates,
|
|
-- sharedtalentutil) et blizzard_spellsearch (filtres, gabarits) :
|
|
-- champ SpellSearchBoxTemplate 184 x 30, 40 lettres ; consigne SEARCH
|
|
-- (SearchBoxTemplate) ; loupe, bord et effacement comme le
|
|
-- grimoire (ForeverUI SpellBookSearch.lua)
|
|
-- options SearchOptionsDropdown (WowStyle1ArrowDropdownTemplate 25 x 25,
|
|
-- fleche common-dropdown-a-button et ses etats) a RIGHT du champ
|
|
-- (3, -2) ; deux cases : CLASS_TALENT_SEARCH_OPTION_HIDE_PASSIVES
|
|
-- "Hide Passives" et _SHOW_RANKS "Show Ranks", non retenues ;
|
|
-- elles ne touchent QUE l'apercu (TransformPreviewResults) :
|
|
-- passifs retires, "Nom (rang/max)"
|
|
-- apercu SpellSearchPreviewContainerTemplate 176 de large, TOPRIGHT
|
|
-- sur le BOTTOMRIGHT du champ (-4, 2) ; filtre de NOM (exact,
|
|
-- contenu) ; la suggestion TALENT_FRAME_SEARCH_NOT_ON_ACTIONBAR
|
|
-- "Missing from action bar" sous 3 lettres
|
|
-- recherche filtre de TEXTE (GetMatchTypeForText) : exact, nom,
|
|
-- description, puis "apparente" (le nom du talent figure dans
|
|
-- la description du talent qui porte exactement le texte) ;
|
|
-- ou le filtre des BARRES : un talent appris, sort actif,
|
|
-- absent d'une barre active
|
|
-- marques pas de liste : chaque noeud trouve porte SearchIcon, 63 x 63
|
|
-- (talents-search-*, taille d'atlas), CENTER sur le TOPRIGHT de
|
|
-- son icone, au-dessus du noeud ; OverlayIcon, la meme en ADD,
|
|
-- bat de 0 a 0,5 en 1 s puis retombe en 1 s ; au survol de son
|
|
-- centre (18 x 18), TALENT_FRAME_SEARCH_TOOLTIP_* :
|
|
-- exact talents-search-exactmatch "Exact search match"
|
|
-- nom, description talents-search-match "Search match"
|
|
-- apparente talents-search-relatedmatch
|
|
-- "Related to the talent you searched for"
|
|
-- absent talents-search-notonactionbar "Not on action bar"
|
|
-- posture inactive talents-search-notonactionbarhidden
|
|
-- "On an action bar belonging to a different stance"
|
|
-- barre desactivee talents-search-notonactionbarhidden
|
|
-- "On a disabled action bar"
|
|
-- mise a jour la recherche se recalcule a chaque changement de l'ecran
|
|
-- (UpdateFullSearchResults)
|
|
--
|
|
-- CE QUI DIFFERE, ET POURQUOI.
|
|
-- * le champ est a GAUCHE du compteur "Unspent Talents" (camelot : a droite,
|
|
-- la ou le compteur a ete remonte a la demande) ; en fenetre reduite il se
|
|
-- retrecit pour tenir (decision du 2026-09-25).
|
|
-- * l'apercu montre 5 resultats (camelot : 3), comme le grimoire
|
|
-- (decision du 2026-09-25).
|
|
-- * 3.3.5 ne donne pas la description d'un talent : elle se lit dans une
|
|
-- infobulle cachee (SetTalent), sans ses lignes de rang, de prerequis ni
|
|
-- "Click to learn".
|
|
-- * pas de champ sur la page des glyphes.
|
|
|
|
ForeverUI = ForeverUI or {}
|
|
|
|
local K = {}
|
|
ForeverUI.TalentsSearch = K
|
|
|
|
local SEP = string.char(92)
|
|
local TEXTE = {
|
|
consigne = SEARCH or "Search",
|
|
pasSurBarre = "Missing from action bar", -- TALENT_FRAME_SEARCH_NOT_ON_ACTIONBAR
|
|
masquerPassifs = "Hide Passives", -- CLASS_TALENT_SEARCH_OPTION_HIDE_PASSIVES
|
|
rangs = "Show Ranks", -- CLASS_TALENT_SEARCH_OPTION_SHOW_RANKS
|
|
depassement = "And %s more", -- TALENT_FRAME_SEARCH_PREVIEW_OVERFLOW_FORMAT
|
|
}
|
|
local MIN_LETTRES = 3 -- MIN_CHARACTER_SEARCH
|
|
|
|
-- SpellSearchUtil.MatchType : le plus grand est le meilleur
|
|
local TYPE = { description = 1, nom = 2, apparente = 3, exact = 4, absent = 5, postureInactive = 6, barreDesactivee = 7 }
|
|
-- SearchMatchStyles (blizzard_sharedtalentutil.lua)
|
|
local STYLES = {
|
|
[TYPE.apparente] = { icone = "talents-search-relatedmatch", info = "Related to the talent you searched for" },
|
|
[TYPE.nom] = { icone = "talents-search-match", info = "Search match" },
|
|
[TYPE.description] = { icone = "talents-search-match", info = "Search match" },
|
|
[TYPE.exact] = { icone = "talents-search-exactmatch", info = "Exact search match" },
|
|
[TYPE.absent] = { icone = "talents-search-notonactionbar", info = "Not on action bar" },
|
|
[TYPE.postureInactive] = { icone = "talents-search-notonactionbarhidden",
|
|
info = "On an action bar belonging to a different stance" },
|
|
[TYPE.barreDesactivee] = { icone = "talents-search-notonactionbarhidden", info = "On a disabled action bar" },
|
|
}
|
|
|
|
local M = {
|
|
boiteL = 184, boiteH = 30, lettres = 40, ecartCompteur = 10, margeGauche = 10,
|
|
fleche = 25, flecheX = 3, flecheY = -2,
|
|
bordL = 8, bordH = 20, bordX = -5, loupe = 10, loupeX = 1, loupeY = -1, gris = 0.6,
|
|
effacer = 17, effacerX = -3, effacerIcone = 10, effacerIconeX = 3, effacerIconeY = -3,
|
|
margeG = 16, margeD = 20, consigneGris = 0.35,
|
|
apercuL = 176, apercuX = -4, apercuY = 2, ligneH = 27, lignes = 5, hautMarge = 1, basMarge = 3,
|
|
ecart = 1, depassementH = 16, depassementY = 5, depassementX = 9,
|
|
cadreIcone = 18, cadreIconeX = 5, cadreIconeY = 1, nomX = 5, nomY = 1, nomD = -5,
|
|
loupeSugg = 14, loupeSuggX = 10, loupeSuggY = 1, texteSuggX = 10,
|
|
-- la marque d'un noeud et son battement
|
|
marque = 63, marqueSurvol = 18, battement = 1, battementAlpha = 0.5,
|
|
-- la liste des options (celle des reglages du grimoire)
|
|
ligneMenuH = 20, bordMenu = 15, ligneMenuX = 11, texteMenuX = 20, largeurPlus = 40, margeMenu = 25,
|
|
case = 12, cocheL = 15, cocheH = 14, cocheX = 2, cocheY = 1,
|
|
fondCoin = 18, fondMarges = { 9, 6, 9, 12 }, fondAlpha = 0.925, attente = 2,
|
|
}
|
|
|
|
-- ------------------------------------------------------------ les chaines
|
|
local function egal(a, b)
|
|
return a and b and string.lower(a) == string.lower(b)
|
|
end
|
|
local function contient(parent, sous)
|
|
return parent and sous and string.find(string.lower(parent), string.lower(sous), 1, true) ~= nil
|
|
end
|
|
|
|
-- ------------------------------------------------------------ la description
|
|
-- l'infobulle d'un talent, sans son nom, ses lignes de rang, de prerequis ni
|
|
-- l'invite d'apprentissage : ce qui reste est la description (et celle du
|
|
-- rang suivant)
|
|
local lecteur = CreateFrame("GameTooltip", "ForeverUITalentsScanTooltip", nil, "GameTooltipTemplate")
|
|
-- un format du client en motif Lua ; ses arguments peuvent etre numerotes
|
|
-- ("Requires %1$d points in %2$s Talents" : TOOLTIP_TALENT_TIER_POINTS)
|
|
local function motif(format)
|
|
if not format then return nil end
|
|
local m = string.gsub(format, "%%%d%$", "%%")
|
|
m = string.gsub(m, "([%(%)%.%+%-%*%?%[%]%^%$])", "%%%1")
|
|
m = string.gsub(m, "%%d", "%%d+")
|
|
m = string.gsub(m, "%%s", ".+")
|
|
return "^" .. m .. "$"
|
|
end
|
|
local LIGNES_ECARTEES = {}
|
|
for _, f in ipairs({ TOOLTIP_TALENT_RANK, TOOLTIP_TALENT_NEXT_RANK, TOOLTIP_TALENT_LEARN,
|
|
TOOLTIP_TALENT_TIER_POINTS, TOOLTIP_TALENT_PREREQ, TOOLTIP_TALENT_UNLEARN }) do
|
|
local m = motif(f)
|
|
if m then table.insert(LIGNES_ECARTEES, m) end
|
|
end
|
|
local descriptions = {}
|
|
local function description(T, t)
|
|
local cle = (T.pet and "p" or "j") .. T.groupe .. ":" .. t.onglet .. ":" .. t.index .. ":" .. t.rang
|
|
local d = descriptions[cle]
|
|
if d == nil then
|
|
local parties = {}
|
|
lecteur:SetOwner(WorldFrame, "ANCHOR_NONE")
|
|
lecteur:ClearLines()
|
|
lecteur:SetTalent(t.onglet, t.index, false, T.pet, T.groupe, true)
|
|
for i = 2, lecteur:NumLines() do
|
|
local ligne = _G["ForeverUITalentsScanTooltipTextLeft" .. i]
|
|
local texte = ligne and ligne:GetText()
|
|
if texte and texte ~= "" then
|
|
local ecartee = false
|
|
for _, m in ipairs(LIGNES_ECARTEES) do
|
|
local ok, trouve = pcall(string.find, texte, m)
|
|
if ok and trouve then ecartee = true break end
|
|
end
|
|
if not ecartee then table.insert(parties, texte) end
|
|
end
|
|
end
|
|
lecteur:Hide()
|
|
d = table.concat(parties, "\n")
|
|
descriptions[cle] = d
|
|
end
|
|
return d
|
|
end
|
|
K.description = description
|
|
|
|
-- ------------------------------------------------------------ l'etat
|
|
K.etat = nil -- { filtre = "texte", texte } | { filtre = "barres" }
|
|
K.options = { passifs = false, rangs = false }
|
|
local T -- ForeverUI.Talents, a la construction
|
|
|
|
-- les talents de l'ecran, dans l'ordre (arbre, palier, colonne)
|
|
local function talents()
|
|
local liste = {}
|
|
for _, o in ipairs(T.onglets or {}) do
|
|
for _, t in ipairs(o.talents) do table.insert(liste, t) end
|
|
end
|
|
table.sort(liste, function(a, b)
|
|
if a.onglet ~= b.onglet then return a.onglet < b.onglet end
|
|
if a.palier ~= b.palier then return a.palier < b.palier end
|
|
return a.colonne < b.colonne
|
|
end)
|
|
return liste
|
|
end
|
|
|
|
-- GetActionbarStatusForSpell : un talent appris (valide), sort actif, qui
|
|
-- n'est sur aucune barre active
|
|
local function typeBarre(t, barres)
|
|
if not t.carre or (t.appris or 0) == 0 then return end
|
|
if T.pet then
|
|
if barres.familier[t.nom] then return end
|
|
return TYPE.absent
|
|
end
|
|
if barres.actives[t.nom] then return end
|
|
if barres.desactivees[t.nom] then return TYPE.barreDesactivee end
|
|
if barres.inactives[t.nom] then return TYPE.postureInactive end
|
|
return TYPE.absent
|
|
end
|
|
|
|
-- les types de la recherche en cours, par talent (onglet:index)
|
|
function K.calculer()
|
|
local etat = K.etat
|
|
local types = {}
|
|
if not etat then return types end
|
|
local tous = talents()
|
|
if etat.filtre == "barres" then
|
|
local R = ForeverUI.SpellBookSearch
|
|
local barres = R and R.barres and R.barres()
|
|
if barres then
|
|
for _, t in ipairs(tous) do
|
|
types[t.onglet .. ":" .. t.index] = typeBarre(t, barres)
|
|
end
|
|
end
|
|
return types
|
|
end
|
|
local texte = etat.texte
|
|
local descExacte
|
|
for _, t in ipairs(tous) do
|
|
if egal(t.nom, texte) then
|
|
descExacte = description(T, t)
|
|
break
|
|
end
|
|
end
|
|
for _, t in ipairs(tous) do
|
|
local ty
|
|
if egal(t.nom, texte) then
|
|
ty = TYPE.exact
|
|
elseif contient(t.nom, texte) then
|
|
ty = TYPE.nom
|
|
elseif contient(description(T, t), texte) then
|
|
ty = TYPE.description
|
|
elseif descExacte and contient(descExacte, t.nom) then
|
|
ty = TYPE.apparente
|
|
end
|
|
types[t.onglet .. ":" .. t.index] = ty
|
|
end
|
|
return types
|
|
end
|
|
|
|
-- l'apercu : le filtre de nom ; puis les options (TransformPreviewResults)
|
|
function K.apercu(texte)
|
|
local trouves = {}
|
|
for _, t in ipairs(talents()) do
|
|
local ty
|
|
if egal(t.nom, texte) then ty = TYPE.exact elseif contient(t.nom, texte) then ty = TYPE.nom end
|
|
if ty and not (K.options.passifs and not t.carre) then
|
|
local nom = t.nom
|
|
if K.options.rangs then nom = nom .. " (" .. t.rang .. "/" .. t.max .. ")" end
|
|
table.insert(trouves, { talent = t, type = ty, nom = nom, cherche = t.nom, icone = t.icone })
|
|
end
|
|
end
|
|
-- PreviewSearchResultSort : le type, puis l'ordre de l'ecran
|
|
local ordre = {}
|
|
for i, r in ipairs(trouves) do ordre[r] = i end
|
|
table.sort(trouves, function(a, b)
|
|
if a.type ~= b.type then return a.type > b.type end
|
|
return ordre[a] < ordre[b]
|
|
end)
|
|
return trouves
|
|
end
|
|
|
|
-- ------------------------------------------------------------ les marques
|
|
local function creerMarque(b)
|
|
local m = CreateFrame("Frame", nil, b)
|
|
m:SetWidth(M.marque)
|
|
m:SetHeight(M.marque)
|
|
m:SetPoint("CENTER", b.icone, "TOPRIGHT", 0, 0)
|
|
m:SetFrameLevel(b:GetFrameLevel() + 50)
|
|
local icone = m:CreateTexture(nil, "OVERLAY")
|
|
icone:SetAllPoints(m)
|
|
local battant = m:CreateTexture(nil, "OVERLAY")
|
|
battant:SetAllPoints(m)
|
|
battant:SetBlendMode("ADD")
|
|
battant:SetAlpha(0)
|
|
m.icone, m.battant = icone, battant
|
|
-- le survol : son centre seulement
|
|
local survol = CreateFrame("Frame", nil, m)
|
|
survol:SetWidth(M.marqueSurvol)
|
|
survol:SetHeight(M.marqueSurvol)
|
|
survol:SetPoint("CENTER", m, "CENTER", 0, 0)
|
|
survol:EnableMouse(true)
|
|
survol:SetScript("OnEnter", function(self)
|
|
if not m.info then return end
|
|
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
|
GameTooltip:SetText(m.info, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
|
|
GameTooltip:Show()
|
|
end)
|
|
survol:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
m.survol = survol
|
|
-- GlowAnim : 0 -> 0,5 en 1 s, puis 0,5 -> 0 en 1 s, en boucle
|
|
m:SetScript("OnUpdate", function(self, ecoule)
|
|
self.temps = ((self.temps or 0) + ecoule) % (2 * M.battement)
|
|
local t = self.temps
|
|
local a = t < M.battement and t / M.battement or 2 - t / M.battement
|
|
self.battant:SetAlpha(a * M.battementAlpha)
|
|
end)
|
|
m:Hide()
|
|
b.marque = m
|
|
-- une marque monte tres haut (noeud + 50) : la croix repasse au-dessus
|
|
if T.leverCroix then T.leverCroix() end
|
|
return m
|
|
end
|
|
|
|
-- SetSearchMatchType sur chaque noeud montre
|
|
local function marquer(types)
|
|
for _, b in pairs(T.arbre and T.arbre.noeuds or {}) do
|
|
local t = b:IsShown() and b.talent
|
|
local ty = t and types[t.onglet .. ":" .. t.index]
|
|
if ty then
|
|
local m = b.marque or creerMarque(b)
|
|
local style = STYLES[ty]
|
|
ForeverUI.SetAtlas(m.icone, style.icone, true)
|
|
ForeverUI.SetAtlas(m.battant, style.icone, true)
|
|
m.info = style.info
|
|
m.type = ty
|
|
m:Show()
|
|
elseif b.marque then
|
|
b.marque.type = nil
|
|
b.marque:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ------------------------------------------------------------ le champ
|
|
local boite, effacer, apercu, fleche, liste
|
|
|
|
local function evaluer()
|
|
local t = boite:GetText() or ""
|
|
if string.len(t) >= MIN_LETTRES then return t end
|
|
end
|
|
|
|
local function cacherApercu()
|
|
apercu:Hide()
|
|
apercu.surligne = 0
|
|
end
|
|
|
|
local function majLoupe()
|
|
local actif = boite:HasFocus() or (boite:GetText() or "") ~= ""
|
|
local g = actif and 1 or M.gris
|
|
boite.loupe:SetVertexColor(g, g, g)
|
|
if (boite:GetText() or "") == "" then boite.consigne:Show() else boite.consigne:Hide() end
|
|
if actif then effacer:Show() else effacer:Hide() end
|
|
end
|
|
|
|
-- SetFullResultSearch
|
|
function K.chercher(texte)
|
|
if not texte then
|
|
K.quitter()
|
|
return
|
|
end
|
|
if egal(texte, TEXTE.pasSurBarre) then
|
|
K.etat = { filtre = "barres" }
|
|
else
|
|
K.etat = { filtre = "texte", texte = texte }
|
|
end
|
|
K.maj()
|
|
end
|
|
|
|
-- ClearActiveSearchState
|
|
function K.quitter()
|
|
K.etat = nil
|
|
if boite then
|
|
boite:ClearFocus()
|
|
boite:SetText("")
|
|
cacherApercu()
|
|
majLoupe()
|
|
end
|
|
K.maj()
|
|
end
|
|
|
|
-- ------------------------------------------------------------ l'apercu
|
|
local function ligneResultat(parent, i)
|
|
local l = CreateFrame("Button", "ForeverUITalentsSearchResult" .. i, parent)
|
|
l:SetHeight(M.ligneH)
|
|
local e = ForeverUI.AtlasEntry("_search-rowbg")
|
|
l:SetNormalTexture(e and e[1] or "")
|
|
ForeverUI.SetAtlas(l:GetNormalTexture(), "_search-rowbg", true)
|
|
l:SetPushedTexture(e and e[1] or "")
|
|
ForeverUI.SetAtlas(l:GetPushedTexture(), "_search-rowbg", true)
|
|
local dessus = CreateFrame("Frame", nil, l)
|
|
dessus:SetAllPoints(l)
|
|
dessus:SetFrameLevel(l:GetFrameLevel() + 1)
|
|
l.dessus = dessus
|
|
local surligne = dessus:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(surligne, "search-highlight", true)
|
|
surligne:SetAllPoints(l)
|
|
surligne:SetBlendMode("ADD")
|
|
surligne:Hide()
|
|
l.surligne = surligne
|
|
return l
|
|
end
|
|
|
|
local function construireApercu(parent)
|
|
local a = CreateFrame("Frame", "ForeverUITalentsSearchPreview", parent)
|
|
a:SetFrameStrata("HIGH")
|
|
a:SetWidth(M.apercuL)
|
|
a:SetPoint("TOPRIGHT", boite, "BOTTOMRIGHT", M.apercuX, M.apercuY)
|
|
a:SetHeight(M.ligneH)
|
|
a:EnableMouse(true)
|
|
a:Hide()
|
|
local fond = a:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(fond, "_search-rowbg", true)
|
|
fond:SetAllPoints(a)
|
|
local coinG = a:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(coinG, "ui-frame-botcornerleft")
|
|
coinG:SetPoint("LEFT", a, "LEFT", -7, 0)
|
|
coinG:SetPoint("BOTTOM", a, "BOTTOM", 0, -7)
|
|
local coinD = a:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(coinD, "ui-frame-botcornerright")
|
|
coinD:SetPoint("BOTTOM", coinG, "BOTTOM", 0, 0)
|
|
coinD:SetPoint("RIGHT", a, "RIGHT", 4, 0)
|
|
local bas = a:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(bas, "_ui-frame-bot", true)
|
|
bas:SetHeight(9)
|
|
bas:SetPoint("BOTTOMLEFT", coinG, "BOTTOMRIGHT", 0, 0)
|
|
bas:SetPoint("BOTTOMRIGHT", coinD, "BOTTOMLEFT", 0, 0)
|
|
local gauche = a:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(gauche, "!ui-frame-lefttile", true)
|
|
gauche:SetWidth(16)
|
|
gauche:SetPoint("BOTTOMLEFT", coinG, "TOPLEFT", 0, 0)
|
|
gauche:SetPoint("TOPLEFT", a, "TOPLEFT", -7, 2)
|
|
local droite = a:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(droite, "!ui-frame-righttile", true)
|
|
droite:SetWidth(10)
|
|
droite:SetPoint("BOTTOMRIGHT", coinD, "TOPRIGHT", 1, 0)
|
|
droite:SetPoint("TOPRIGHT", a, "TOPRIGHT", 5, 2)
|
|
|
|
a.lignes = {}
|
|
for i = 1, M.lignes do
|
|
local l = ligneResultat(a, i)
|
|
l:SetPoint("TOPLEFT", a, "TOPLEFT", 0, -M.hautMarge - (i - 1) * (M.ligneH + M.ecart))
|
|
l:SetPoint("TOPRIGHT", a, "TOPRIGHT", 0, -M.hautMarge - (i - 1) * (M.ligneH + M.ecart))
|
|
local cadre = l.dessus:CreateTexture(nil, "ARTWORK")
|
|
ForeverUI.SetAtlas(cadre, "talents-search-suggestion-itemborder")
|
|
cadre:SetWidth(M.cadreIcone)
|
|
cadre:SetHeight(M.cadreIcone)
|
|
cadre:SetPoint("LEFT", l, "LEFT", M.cadreIconeX, M.cadreIconeY)
|
|
local icone = l:CreateTexture(nil, "OVERLAY")
|
|
icone:SetPoint("TOPLEFT", cadre, "TOPLEFT", 1, -1)
|
|
icone:SetPoint("BOTTOMRIGHT", cadre, "BOTTOMRIGHT", -1, 1)
|
|
local nom = l:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
|
nom:SetJustifyH("LEFT")
|
|
nom:SetPoint("LEFT", icone, "RIGHT", M.nomX, M.nomY)
|
|
nom:SetPoint("RIGHT", l, "RIGHT", M.nomD, 0)
|
|
l.icone, l.nom = icone, nom
|
|
l:SetScript("OnEnter", function() K.surligner(i) end)
|
|
l:SetScript("OnClick", function() K.choisir(i) end)
|
|
l:Hide()
|
|
a.lignes[i] = l
|
|
end
|
|
local sugg = ligneResultat(a, "Suggestion")
|
|
sugg:SetPoint("TOPLEFT", a, "TOPLEFT", 0, 0)
|
|
sugg:SetPoint("TOPRIGHT", a, "TOPRIGHT", 0, 0)
|
|
local loupe = sugg:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(loupe, "talents-search-suggestion-magnifyingglass", true)
|
|
loupe:SetWidth(M.loupeSugg)
|
|
loupe:SetHeight(M.loupeSugg)
|
|
loupe:SetPoint("LEFT", sugg, "LEFT", M.loupeSuggX, M.loupeSuggY)
|
|
local texte = sugg:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
|
texte:SetJustifyH("LEFT")
|
|
texte:SetPoint("LEFT", loupe, "RIGHT", M.texteSuggX, 0)
|
|
texte:SetPoint("RIGHT", sugg, "RIGHT", -5, 0)
|
|
texte:SetText(TEXTE.pasSurBarre)
|
|
sugg.texte = texte
|
|
sugg:SetScript("OnEnter", function() K.surligner(1) end)
|
|
sugg:SetScript("OnClick", function() K.choisir(1) end)
|
|
sugg:Hide()
|
|
a.suggestion = sugg
|
|
local plus = CreateFrame("Frame", nil, a)
|
|
plus:SetHeight(M.depassementH)
|
|
plus:SetPoint("BOTTOMLEFT", a, "BOTTOMLEFT", 0, M.depassementY)
|
|
plus:SetPoint("BOTTOMRIGHT", a, "BOTTOMRIGHT", 0, M.depassementY)
|
|
local plusTexte = plus:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
|
|
plusTexte:SetJustifyH("LEFT")
|
|
plusTexte:SetPoint("TOPLEFT", plus, "TOPLEFT", M.depassementX, 0)
|
|
plusTexte:SetPoint("BOTTOMRIGHT", plus, "BOTTOMRIGHT", 0, 0)
|
|
plus.texte = plusTexte
|
|
plus:Hide()
|
|
a.depassement = plus
|
|
a.surligne = 0
|
|
a:SetScript("OnUpdate", function(self)
|
|
if not boite:HasFocus() and not MouseIsOver(self) then cacherApercu() end
|
|
end)
|
|
return a
|
|
end
|
|
|
|
function K.majApercu(texte)
|
|
local a = apercu
|
|
a.surligne = 0
|
|
a.resultats = nil
|
|
for _, l in ipairs(a.lignes) do l:Hide() l.surligne:Hide() end
|
|
a.suggestion:Hide()
|
|
a.suggestion.surligne:Hide()
|
|
a.depassement:Hide()
|
|
if not texte then
|
|
a.suggestion:Show()
|
|
a.nombre = 1
|
|
a:SetHeight(M.ligneH + M.basMarge)
|
|
a:Show()
|
|
return
|
|
end
|
|
local trouves = K.apercu(texte)
|
|
if #trouves == 0 then
|
|
cacherApercu()
|
|
return
|
|
end
|
|
a.resultats = trouves
|
|
local n = math.min(#trouves, M.lignes)
|
|
for i = 1, n do
|
|
local l = a.lignes[i]
|
|
l.nom:SetText(trouves[i].nom)
|
|
l.icone:SetTexture(trouves[i].icone)
|
|
l:Show()
|
|
end
|
|
a.nombre = n
|
|
local h = M.hautMarge + n * M.ligneH + (n - 1) * M.ecart + M.basMarge
|
|
if #trouves > M.lignes then
|
|
a.depassement.texte:SetText(string.format(TEXTE.depassement, #trouves - M.lignes))
|
|
a.depassement:Show()
|
|
h = h + M.depassementH
|
|
end
|
|
a:SetHeight(h)
|
|
a:Show()
|
|
end
|
|
|
|
function K.surligner(i)
|
|
local a = apercu
|
|
a.surligne = i
|
|
if a.resultats then
|
|
for j, l in ipairs(a.lignes) do
|
|
if j == i then l.surligne:Show() else l.surligne:Hide() end
|
|
end
|
|
else
|
|
if i == 1 then a.suggestion.surligne:Show() else a.suggestion.surligne:Hide() end
|
|
end
|
|
end
|
|
|
|
local function parcourir(sens)
|
|
local a = apercu
|
|
if not a:IsShown() or not a.nombre or a.nombre == 0 then return end
|
|
local n, i = a.nombre, a.surligne or 0
|
|
if sens < 0 then i = (i - 2) % n + 1 else i = i % n + 1 end
|
|
K.surligner(i)
|
|
end
|
|
|
|
-- OnPreviewSearchResultClicked : le nom d'origine (sans "(rang/max)")
|
|
function K.choisir(i)
|
|
local a = apercu
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
local texte
|
|
if a.resultats then
|
|
local r = a.resultats[i]
|
|
if not r then return end
|
|
texte = r.cherche
|
|
else
|
|
texte = TEXTE.pasSurBarre
|
|
end
|
|
boite:ClearFocus()
|
|
boite:SetText(texte)
|
|
cacherApercu()
|
|
K.chercher(texte)
|
|
end
|
|
|
|
-- ------------------------------------------------------------ les options
|
|
local function majFleche()
|
|
local etat = "common-dropdown-a-button"
|
|
if fleche.enfonce and fleche.survol then
|
|
etat = "common-dropdown-a-button-pressedhover"
|
|
elseif fleche.survol then
|
|
etat = "common-dropdown-a-button-hover"
|
|
elseif fleche.enfonce then
|
|
etat = "common-dropdown-a-button-pressed"
|
|
elseif liste and liste:IsShown() then
|
|
etat = "common-dropdown-a-button-open"
|
|
end
|
|
ForeverUI.SetAtlas(fleche.icone, etat)
|
|
end
|
|
|
|
local function majCoches()
|
|
for _, l in ipairs(liste.lignes) do
|
|
if K.options[l.cle] then l.coche:Show() else l.coche:Hide() end
|
|
end
|
|
end
|
|
|
|
local function construireOptions(parent)
|
|
local b = CreateFrame("Button", "ForeverUITalentsSearchOptions", parent)
|
|
fleche = b
|
|
b:SetWidth(M.fleche)
|
|
b:SetHeight(M.fleche)
|
|
b:SetPoint("LEFT", boite, "RIGHT", M.flecheX, M.flecheY)
|
|
b.icone = b:CreateTexture(nil, "OVERLAY")
|
|
b.icone:SetPoint("CENTER", b, "CENTER", 0, -2)
|
|
b:SetScript("OnEnter", function(self) self.survol = true majFleche() end)
|
|
b:SetScript("OnLeave", function(self) self.survol = false majFleche() end)
|
|
b:SetScript("OnMouseDown", function(self) self.enfonce = true majFleche() end)
|
|
b:SetScript("OnMouseUp", function(self) self.enfonce = false majFleche() end)
|
|
b:SetScript("OnClick", function()
|
|
if liste:IsShown() then liste:Hide() else liste.attente = 0 liste:Show() end
|
|
majFleche()
|
|
end)
|
|
|
|
local l0 = CreateFrame("Frame", "ForeverUITalentsSearchOptionsList", parent)
|
|
liste = l0
|
|
l0:SetFrameStrata("DIALOG")
|
|
l0:EnableMouse(true)
|
|
l0:SetPoint("TOPLEFT", b, "BOTTOMLEFT", 0, 0)
|
|
l0:Hide()
|
|
local tranches = ForeverUI.CreateNineSlice(l0, "common-dropdown-bg-c60", M.fondCoin, M.fondMarges, "BACKGROUND")
|
|
for _, t in ipairs(tranches or {}) do t:SetAlpha(M.fondAlpha) end
|
|
l0.lignes = {}
|
|
local plusLong = 0
|
|
for i, def in ipairs({ { cle = "passifs", texte = TEXTE.masquerPassifs }, { cle = "rangs", texte = TEXTE.rangs } }) do
|
|
local l = CreateFrame("Button", "ForeverUITalentsSearchOption" .. i, l0)
|
|
l:SetHeight(M.ligneMenuH)
|
|
l:SetPoint("TOPLEFT", l0, "TOPLEFT", M.ligneMenuX, -M.bordMenu - (i - 1) * M.ligneMenuH)
|
|
l:SetHighlightTexture("Interface" .. SEP .. "QuestFrame" .. SEP .. "UI-QuestTitleHighlight")
|
|
l:GetHighlightTexture():SetBlendMode("ADD")
|
|
local texte = l:CreateFontString(nil, "ARTWORK", "GameFontHighlightLeft")
|
|
texte:SetPoint("LEFT", l, "LEFT", M.texteMenuX, 0)
|
|
texte:SetText(def.texte)
|
|
plusLong = math.max(plusLong, texte:GetStringWidth() or 0)
|
|
local case = l:CreateTexture(nil, "BORDER")
|
|
ForeverUI.SetAtlas(case, "common-dropdown-ticksquare", true)
|
|
case:SetWidth(M.case)
|
|
case:SetHeight(M.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(M.cocheL)
|
|
coche:SetHeight(M.cocheH)
|
|
coche:SetPoint("CENTER", case, "CENTER", M.cocheX, M.cocheY)
|
|
coche:Hide()
|
|
l.cle, l.texte, l.coche = def.cle, texte, coche
|
|
-- CreateCheckbox : la case bascule, la liste reste ouverte ; l'apercu
|
|
-- ouvert se refait
|
|
l:SetScript("OnClick", function(self)
|
|
PlaySound("UChatScrollButton")
|
|
K.options[self.cle] = not K.options[self.cle]
|
|
majCoches()
|
|
if boite:HasFocus() then K.majApercu(evaluer()) end
|
|
end)
|
|
l0.lignes[i] = l
|
|
end
|
|
local largeur = plusLong + M.largeurPlus + M.margeMenu
|
|
l0:SetWidth(largeur)
|
|
l0:SetHeight(#l0.lignes * M.ligneMenuH + 2 * M.bordMenu)
|
|
for _, l in ipairs(l0.lignes) do l:SetWidth(largeur - M.margeMenu) end
|
|
-- elle se ferme 2 s apres que la souris l'a quittee (et la fleche)
|
|
l0:SetScript("OnUpdate", function(self, ecoule)
|
|
if MouseIsOver(self) or MouseIsOver(fleche) then
|
|
self.attente = 0
|
|
else
|
|
self.attente = (self.attente or 0) + ecoule
|
|
if self.attente >= M.attente then self:Hide() end
|
|
end
|
|
end)
|
|
l0:SetScript("OnShow", majCoches)
|
|
l0:SetScript("OnHide", majFleche)
|
|
majFleche()
|
|
end
|
|
|
|
-- ------------------------------------------------------------ construction
|
|
function K.construire(talentsModule)
|
|
if boite then return end
|
|
T = talentsModule
|
|
local parent = T.cadre
|
|
local b = CreateFrame("EditBox", "ForeverUITalentsSearchBox", parent)
|
|
boite = b
|
|
b:SetAutoFocus(false)
|
|
b:SetMaxLetters(M.lettres)
|
|
b:SetHeight(M.boiteH)
|
|
b:SetWidth(M.boiteL)
|
|
b:SetFrameLevel(parent:GetFrameLevel() + 6)
|
|
b:SetFontObject(GameFontHighlightSmall)
|
|
b:SetTextInsets(M.margeG, M.margeD, 0, 0)
|
|
local g = b:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(g, "common-search-border-left", true)
|
|
g:SetWidth(M.bordL) g:SetHeight(M.bordH)
|
|
g:SetPoint("LEFT", b, "LEFT", M.bordX, 0)
|
|
local d = b:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(d, "common-search-border-right", true)
|
|
d:SetWidth(M.bordL) d:SetHeight(M.bordH)
|
|
d:SetPoint("RIGHT", b, "RIGHT", 0, 0)
|
|
local m = b:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(m, "common-search-border-middle", true)
|
|
m:SetHeight(M.bordH)
|
|
m:SetPoint("LEFT", g, "RIGHT", 0, 0)
|
|
m:SetPoint("RIGHT", d, "LEFT", 0, 0)
|
|
local loupe = b:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(loupe, "common-search-magnifyingglass", true)
|
|
loupe:SetWidth(M.loupe) loupe:SetHeight(M.loupe)
|
|
loupe:SetPoint("LEFT", b, "LEFT", M.loupeX, M.loupeY)
|
|
b.loupe = loupe
|
|
local consigne = b:CreateFontString(nil, "ARTWORK", "GameFontDisableSmall")
|
|
consigne:SetJustifyH("LEFT")
|
|
consigne:SetJustifyV("MIDDLE")
|
|
consigne:SetPoint("TOPLEFT", b, "TOPLEFT", M.margeG, 0)
|
|
consigne:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", -M.margeD, 0)
|
|
consigne:SetTextColor(M.consigneGris, M.consigneGris, M.consigneGris)
|
|
consigne:SetText(TEXTE.consigne)
|
|
b.consigne = consigne
|
|
|
|
local e = CreateFrame("Button", "ForeverUITalentsSearchClear", b)
|
|
effacer = e
|
|
e:SetWidth(M.effacer) e:SetHeight(M.effacer)
|
|
e:SetPoint("RIGHT", b, "RIGHT", M.effacerX, 0)
|
|
e:SetFrameLevel(b:GetFrameLevel() + 2)
|
|
local icone = e:CreateTexture(nil, "ARTWORK")
|
|
ForeverUI.SetAtlas(icone, "common-search-clearbutton", true)
|
|
icone:SetWidth(M.effacerIcone) icone:SetHeight(M.effacerIcone)
|
|
icone:SetPoint("TOPLEFT", e, "TOPLEFT", M.effacerIconeX, M.effacerIconeY)
|
|
icone:SetAlpha(0.5)
|
|
e.icone = icone
|
|
e:SetScript("OnEnter", function() icone:SetAlpha(1) end)
|
|
e:SetScript("OnLeave", function() icone:SetAlpha(0.5) end)
|
|
e:SetScript("OnMouseDown", function() icone:SetPoint("TOPLEFT", e, "TOPLEFT", M.effacerIconeX + 1, M.effacerIconeY - 1) end)
|
|
e:SetScript("OnMouseUp", function() icone:SetPoint("TOPLEFT", e, "TOPLEFT", M.effacerIconeX, M.effacerIconeY) end)
|
|
e:SetScript("OnClick", function()
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
K.quitter()
|
|
end)
|
|
e:Hide()
|
|
|
|
construireOptions(parent)
|
|
fleche:SetFrameLevel(b:GetFrameLevel())
|
|
-- la place : la fleche contre le libelle du compteur, le champ contre la
|
|
-- fleche (camelot : la fleche a RIGHT du champ en (3, -2))
|
|
fleche:ClearAllPoints()
|
|
fleche:SetPoint("RIGHT", T.points.libelle, "LEFT", -M.ecartCompteur, M.flecheY - 1)
|
|
b:SetPoint("RIGHT", fleche, "LEFT", -M.flecheX, -M.flecheY)
|
|
|
|
apercu = construireApercu(parent)
|
|
|
|
b:SetScript("OnEditFocusGained", function()
|
|
majLoupe()
|
|
K.majApercu(evaluer())
|
|
end)
|
|
b:SetScript("OnEditFocusLost", function()
|
|
majLoupe()
|
|
if not MouseIsOver(apercu) then cacherApercu() end
|
|
end)
|
|
b:SetScript("OnTextChanged", function(self)
|
|
majLoupe()
|
|
if self:HasFocus() then K.majApercu(evaluer()) end
|
|
end)
|
|
b:SetScript("OnEnterPressed", function(self)
|
|
local a = apercu
|
|
if a:IsShown() and (a.surligne or 0) > 0 then
|
|
K.choisir(a.surligne)
|
|
return
|
|
end
|
|
cacherApercu()
|
|
K.chercher(evaluer())
|
|
self:ClearFocus()
|
|
end)
|
|
b:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
|
|
b:SetScript("OnKeyDown", function(_, touche)
|
|
if touche == "UP" then parcourir(-1) elseif touche == "DOWN" then parcourir(1) end
|
|
end)
|
|
majLoupe()
|
|
K.boite, K.effacer, K.apercuCadre, K.fleche, K.liste = boite, effacer, apercu, fleche, liste
|
|
end
|
|
|
|
-- la largeur : 184, ou ce qui reste a gauche du compteur (fenetre reduite)
|
|
function K.poser(largeurPage)
|
|
if not boite then return end
|
|
local libelle = T.points.libelle
|
|
local droiteLibelle = T.points.droiteLibelle or 0
|
|
local w = libelle:GetStringWidth() or 0
|
|
-- du bord gauche du cadre (page + 2 de chaque cote) a la gauche du champ
|
|
local reste = largeurPage + 4 + droiteLibelle - w - M.ecartCompteur - M.fleche - M.flecheX - M.margeGauche
|
|
boite:SetWidth(math.max(60, math.min(M.boiteL, reste)))
|
|
end
|
|
|
|
-- apres chaque mise a jour de l'ecran : les marques (UpdateFullSearchResults) ;
|
|
-- rien sur la page des glyphes
|
|
function K.maj()
|
|
if not boite or not T then return end
|
|
if T.glyphes then
|
|
boite:Hide()
|
|
fleche:Hide()
|
|
liste:Hide()
|
|
cacherApercu()
|
|
marquer({})
|
|
return
|
|
end
|
|
boite:Show()
|
|
fleche:Show()
|
|
marquer(K.calculer())
|
|
end
|
|
|
|
-- LES BARRES CHANGENT (un sort pose, retire, une page ou une posture) : la
|
|
-- recherche "Missing from action bar" se refait, et le talent qu'on vient de
|
|
-- poser perd sa marque (demande du 2026-09-25)
|
|
local veille = CreateFrame("Frame")
|
|
for _, ev in ipairs({ "ACTIONBAR_SLOT_CHANGED", "ACTIONBAR_PAGE_CHANGED", "UPDATE_BONUS_ACTIONBAR",
|
|
"ACTIONBAR_SHOWGRID", "ACTIONBAR_HIDEGRID", "PET_BAR_UPDATE", "UPDATE_MULTI_ACTIONBAR" }) do
|
|
veille:RegisterEvent(ev)
|
|
end
|
|
veille:SetScript("OnEvent", function()
|
|
if K.etat and K.etat.filtre == "barres" and T and T.livre and T.livre:IsVisible() then
|
|
K.maj()
|
|
end
|
|
end)
|
|
K.veilleBarres = veille
|
|
|
|
-- les talents deja construits (Blizzard_TalentUI charge avant ce fichier)
|
|
if ForeverUI.Talents and ForeverUI.Talents.livre then
|
|
K.construire(ForeverUI.Talents)
|
|
ForeverUI.Talents.maj()
|
|
end
|