Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d5e2dce359 | |||
| 22ae941c4a | |||
| b0da9ec10b | |||
| d8d94f10b0 | |||
| 255485daca |
@@ -5,4 +5,8 @@ AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=
|
||||
AWS_BUCKET=
|
||||
AWS_ENDPOINT=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=
|
||||
|
||||
PRODUCTION_REALMLIST=
|
||||
PTR_REALMLIST=
|
||||
LOCAL_REALMLIST=
|
||||
@@ -0,0 +1,276 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
C_PlayerGuide.lua
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
Клиентское хранилище данных Путеводителя + UI-facing API.
|
||||
Соответствует контракту из player-guide-requirements.md → "Client API".
|
||||
|
||||
Подключается до PlayerGuide.xml/.lua в .toc.
|
||||
------------------------------------------------------------------------------]]
|
||||
|
||||
C_PlayerGuide = C_PlayerGuide or {}
|
||||
local L = C_PlayerGuide
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Состояние
|
||||
|
||||
local currentData = nil -- последний валидный payload от сервера
|
||||
local lastError = nil -- строка с описанием последней проблемы валидации
|
||||
|
||||
-- Состояние UI: "data" | "empty" | "loading"
|
||||
local uiState = "loading"
|
||||
|
||||
-- Подписчики на обновления (PlayerGuide.lua вешает сюда свой Refresh)
|
||||
local listeners = {}
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Утилиты
|
||||
|
||||
local function notify()
|
||||
for _, fn in ipairs(listeners) do
|
||||
-- pcall чтобы один сломанный listener не уронил остальные
|
||||
pcall(fn, uiState, currentData)
|
||||
end
|
||||
end
|
||||
|
||||
local function isTable(v) return type(v) == "table" end
|
||||
|
||||
-- Валидация payload'а. Возвращает true либо false + строка ошибки.
|
||||
local function validate(data)
|
||||
if not isTable(data) then return false, "payload is not a table" end
|
||||
if data.stageID and type(data.stageID) ~= "number" then
|
||||
return false, "stageID is not a number"
|
||||
end
|
||||
if data.progress and type(data.progress) ~= "number" then
|
||||
return false, "progress is not a number"
|
||||
end
|
||||
if data.objectives and not isTable(data.objectives) then
|
||||
return false, "objectives is not a table"
|
||||
end
|
||||
-- Уникальность id целей
|
||||
if isTable(data.objectives) then
|
||||
local seen = {}
|
||||
for i, o in ipairs(data.objectives) do
|
||||
if not isTable(o) then
|
||||
return false, "objective #" .. i .. " is not a table"
|
||||
end
|
||||
if o.id and seen[o.id] then
|
||||
return false, "duplicate objective id: " .. tostring(o.id)
|
||||
end
|
||||
if o.id then seen[o.id] = true end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Подписки (для PlayerGuide.lua)
|
||||
|
||||
function L.RegisterListener(fn)
|
||||
table.insert(listeners, fn)
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Публичный API (см. требования → Client API)
|
||||
|
||||
function L.SetData(data)
|
||||
local ok, err = validate(data)
|
||||
if not ok then
|
||||
lastError = err
|
||||
uiState = "empty"
|
||||
notify()
|
||||
return false, err
|
||||
end
|
||||
currentData = data
|
||||
lastError = nil
|
||||
uiState = "data"
|
||||
notify()
|
||||
return true
|
||||
end
|
||||
|
||||
function L.Clear()
|
||||
currentData = nil
|
||||
lastError = nil
|
||||
uiState = "empty"
|
||||
notify()
|
||||
end
|
||||
|
||||
function L.SetLoading()
|
||||
if uiState ~= "data" then
|
||||
uiState = "loading"
|
||||
notify()
|
||||
end
|
||||
end
|
||||
|
||||
function L.GetState()
|
||||
return uiState
|
||||
end
|
||||
|
||||
function L.GetLastError()
|
||||
return lastError
|
||||
end
|
||||
|
||||
function L.HasData()
|
||||
return uiState == "data" and currentData ~= nil
|
||||
end
|
||||
|
||||
function L.GetStageInfo()
|
||||
if not L.HasData() then return nil end
|
||||
return currentData.stageID,
|
||||
currentData.stageName,
|
||||
currentData.stageDescription,
|
||||
currentData.progress or 0
|
||||
end
|
||||
|
||||
-- Доп. метод: список всех этапов (для цепочки сверху)
|
||||
function L.GetStages()
|
||||
if not L.HasData() then return nil end
|
||||
return currentData.stages or {} -- сервер опционально шлёт массив этапов
|
||||
end
|
||||
|
||||
function L.GetNumObjectives()
|
||||
if not L.HasData() or not currentData.objectives then return 0 end
|
||||
return #currentData.objectives
|
||||
end
|
||||
|
||||
function L.GetObjectiveInfo(index)
|
||||
if not L.HasData() or not currentData.objectives then return nil end
|
||||
local o = currentData.objectives[index]
|
||||
if not o then return nil end
|
||||
return {
|
||||
id = o.id,
|
||||
type = o.type or "custom",
|
||||
title = o.title or "",
|
||||
description = o.description or o.subtitle or "",
|
||||
subtitle = o.subtitle, -- наше расширение поля для UI
|
||||
completed = o.completed and true or false,
|
||||
progress = o.progress or 0,
|
||||
required = o.required or 1,
|
||||
targetID = o.targetID,
|
||||
questID = o.questID,
|
||||
instanceID = o.instanceID,
|
||||
encounterID = o.encounterID,
|
||||
achievementID = o.achievementID,
|
||||
factionID = o.factionID,
|
||||
itemID = o.itemID,
|
||||
currencyID = o.currencyID,
|
||||
order = o.order or index,
|
||||
}
|
||||
end
|
||||
|
||||
function L.GetNumRecommendations()
|
||||
if not L.HasData() or not currentData.recommendations then return 0 end
|
||||
return #currentData.recommendations
|
||||
end
|
||||
|
||||
function L.GetRecommendationInfo(index)
|
||||
if not L.HasData() or not currentData.recommendations then return nil end
|
||||
local r = currentData.recommendations[index]
|
||||
if not r then return nil end
|
||||
return {
|
||||
id = r.id,
|
||||
type = r.type or "dungeon",
|
||||
title = r.title or r.name or "",
|
||||
name = r.name or r.title or "",
|
||||
description = r.description or r.loc or "",
|
||||
loc = r.loc, -- локация/подзаголовок
|
||||
level = r.level,
|
||||
biome = r.biome, -- ключ биом-обложки
|
||||
instanceID = r.instanceID,
|
||||
encounterID = r.encounterID,
|
||||
priority = r.priority or 0,
|
||||
}
|
||||
end
|
||||
|
||||
function L.GetNumRewards()
|
||||
if not L.HasData() or not currentData.rewards then return 0 end
|
||||
return #currentData.rewards
|
||||
end
|
||||
|
||||
function L.GetRewardInfo(index)
|
||||
if not L.HasData() or not currentData.rewards then return nil end
|
||||
local r = currentData.rewards[index]
|
||||
if not r then return nil end
|
||||
return r
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Сетевой слой (AIO)
|
||||
-- MoonWellPlayerGuide.lua перехватывает SetData() из serverToClient и вызывает
|
||||
-- L.SetData(payload). RequestRefresh шлёт обратно в обратную сторону.
|
||||
|
||||
function L.RequestRefresh()
|
||||
L.SetLoading()
|
||||
if AIO and AIO.Handle then
|
||||
-- name берётся из serverToClient на сервере. Тут — лёгкая обёртка.
|
||||
AIO.Handle("MoonWellPlayerGuide", "RequestRefresh")
|
||||
else
|
||||
-- Без AIO (например, в режиме разработки) — отдаём заглушку через
|
||||
-- 0.5 секунды, чтобы UI не висел в Loading навечно.
|
||||
if C_Timer and C_Timer.After then
|
||||
C_Timer.After(0.5, function() L.Clear() end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function L.RequestTrackObjective(objectiveID)
|
||||
if AIO and AIO.Handle then
|
||||
AIO.Handle("MoonWellPlayerGuide", "RequestTrackObjective", objectiveID)
|
||||
end
|
||||
end
|
||||
|
||||
function L.RequestOpenTarget(objectiveID)
|
||||
if AIO and AIO.Handle then
|
||||
AIO.Handle("MoonWellPlayerGuide", "RequestOpenTarget", objectiveID)
|
||||
end
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Статика для разработки (видна, пока сервер не прислал реальный payload).
|
||||
-- Можно убрать перед релизом.
|
||||
|
||||
function L._InjectDevPayload()
|
||||
L.SetData({
|
||||
version = 1,
|
||||
stageID = 5,
|
||||
stageName = "Подготовка к Нордсколу",
|
||||
stageDescription =
|
||||
"Завершите ключевые цепочки заданий в Запределье и докажите готовность к походу на север. "..
|
||||
"По прибытии в Нордскол вам понадобится снаряжение героического уровня.",
|
||||
progress = 62,
|
||||
stages = {
|
||||
{ id = 1, name = "Начало пути", short = "Начало пути", era = "1—58", status = "done" },
|
||||
{ id = 2, name = "Огненные Недра", short = "Огненные Недра", era = "60", status = "done" },
|
||||
{ id = 3, name = "Сквозь Тёмный портал", short = "Тёмный портал", era = "58—60", status = "done" },
|
||||
{ id = 4, name = "Покорение Запределья", short = "Запределье", era = "60—68", status = "done" },
|
||||
{ id = 5, name = "Подготовка к Нордсколу", short = "К Нордсколу", era = "68—70", status = "active" },
|
||||
{ id = 6, name = "Заря Нордскола", short = "Нордскол", era = "70—78", status = "locked" },
|
||||
{ id = 7, name = "Цитадель Ледяной Короны",short = "Ледяная Корона", era = "78—80", status = "locked" },
|
||||
},
|
||||
objectives = {
|
||||
{ id = 1, type = "level", title = "Достигнуть 68 уровня",
|
||||
subtitle = "Текущий уровень: 68. Можно отправляться в Нордскол.",
|
||||
completed = true, progress = 68, required = 68, order = 1 },
|
||||
{ id = 2, type = "quest_chain", title = "Цепочка «Поиск виверны» в Награнде",
|
||||
subtitle = "Кисть Шамана • Награнд • награда: «Привязка к виверне»",
|
||||
completed = false, progress = 5, required = 8, order = 2 },
|
||||
{ id = 3, type = "dungeon", title = "Пройти Кузню Крови",
|
||||
subtitle = "Цитадель Адского Пламени • рек. уровень 61—64",
|
||||
completed = false, progress = 0, required = 1, order = 3, instanceID = 256 },
|
||||
{ id = 4, type = "dungeon", title = "Пройти Аукиндон: Подземелья Аукенай",
|
||||
subtitle = "Награнд • рек. уровень 64—66",
|
||||
completed = false, progress = 0, required = 1, order = 4, instanceID = 257 },
|
||||
{ id = 5, type = "reputation", title = "Получить «Дружелюбие» с Кенарийской экспедицией",
|
||||
subtitle = "Текущее положение: Нейтрально • Долина Призрачной Луны",
|
||||
completed = false, progress = 1200, required = 3000, order = 5, factionID = 942 },
|
||||
},
|
||||
recommendations = {
|
||||
{ id = 1, type = "dungeon", name = "Кузня Крови", loc = "Цитадель Адского Пламени", level = "61—64", biome = "fire" },
|
||||
{ id = 2, type = "dungeon", name = "Подземелья Аукенай", loc = "Аукиндон", level = "64—66", biome = "arcane" },
|
||||
{ id = 3, type = "dungeon", name = "Сетеккские залы", loc = "Награнд", level = "67—69", biome = "storm" },
|
||||
{ id = 4, type = "dungeon", name = "Темный Лабиринт", loc = "Аукиндон", level = "68—70", biome = "shadow" },
|
||||
{ id = 5, type = "dungeon", name = "Ботаника", loc = "Долина Призрачной Луны", level = "69—70", biome = "nature" },
|
||||
{ id = 6, type = "dungeon", name = "Расколотые Залы", loc = "Цитадель Адского Пламени", level = "70 H", biome = "fire" },
|
||||
},
|
||||
rewards = {}, -- блок наград временно скрыт (см. SHOW_REWARDS в PlayerGuide.lua)
|
||||
})
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
AIO/MoonWellPlayerGuide.lua
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
AIO-handler (клиентская часть) для namespace "MoonWellPlayerGuide".
|
||||
Сервер отправляет SetData(payload), клиент кладёт его в C_PlayerGuide.
|
||||
Клиент шлёт RequestRefresh обратно при нажатии «Обновить».
|
||||
|
||||
Контракт описан в player-guide-requirements.md → "AIO Contract".
|
||||
------------------------------------------------------------------------------]]
|
||||
|
||||
local AIO = AIO or require("AIO")
|
||||
if not AIO then return end -- безопасный no-op без AIO
|
||||
|
||||
local handlers = AIO.AddHandlers("MoonWellPlayerGuide", {})
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Server → Client
|
||||
|
||||
-- Полная замена payload
|
||||
function handlers.SetData(player, payload)
|
||||
if C_PlayerGuide and C_PlayerGuide.SetData then
|
||||
local ok, err = C_PlayerGuide.SetData(payload)
|
||||
if not ok and DEFAULT_CHAT_FRAME then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
"|cffff8080[Путеводитель]|r Ошибка payload: "..tostring(err))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Обновление одной цели (для частичных update'ов)
|
||||
function handlers.UpdateObjective(player, objective)
|
||||
if not (C_PlayerGuide and C_PlayerGuide.HasData()) then return end
|
||||
-- Простая стратегия: пересобрать payload локально с заменой цели по id.
|
||||
-- C_PlayerGuide хранит данные internally — даём прямой доступ через _patch:
|
||||
local n = C_PlayerGuide.GetNumObjectives()
|
||||
for i = 1, n do
|
||||
local o = C_PlayerGuide.GetObjectiveInfo(i)
|
||||
if o and o.id == objective.id then
|
||||
-- Грязный, но рабочий способ для MVP — отдать обратно весь массив.
|
||||
-- Для прод-уровня лучше выставить C_PlayerGuide.PatchObjective(o).
|
||||
o.progress = objective.progress or o.progress
|
||||
o.required = objective.required or o.required
|
||||
o.completed = objective.completed
|
||||
if MW_PlayerGuide and MW_PlayerGuide.Refresh then
|
||||
MW_PlayerGuide.Refresh()
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Замена только инфы об этапе (без массива objectives)
|
||||
function handlers.SetStage(player, stagePayload)
|
||||
if not stagePayload then return end
|
||||
-- Объединяем с текущим payload'ом
|
||||
local _, name, desc, progress = C_PlayerGuide.GetStageInfo()
|
||||
C_PlayerGuide.SetData({
|
||||
stageID = stagePayload.stageID,
|
||||
stageName = stagePayload.stageName or name,
|
||||
stageDescription = stagePayload.stageDescription or desc,
|
||||
progress = stagePayload.progress or progress,
|
||||
stages = stagePayload.stages,
|
||||
objectives = stagePayload.objectives or {},
|
||||
recommendations = stagePayload.recommendations or {},
|
||||
rewards = stagePayload.rewards or {},
|
||||
})
|
||||
end
|
||||
|
||||
-- Сообщения от сервера (баблы / chat-фрейм)
|
||||
function handlers.Notify(player, message)
|
||||
if DEFAULT_CHAT_FRAME and message then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffffd76a[Путеводитель]|r "..tostring(message))
|
||||
end
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Client → Server: ничего регистрировать не нужно — это просто AIO.Handle
|
||||
-- внутри C_PlayerGuide.RequestRefresh / RequestTrackObjective / RequestOpenTarget.
|
||||
@@ -0,0 +1,402 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
PlayerGuide.lua
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
UI-логика вкладки «Путеводитель». Парный файл PlayerGuide.xml.
|
||||
|
||||
Зависимости:
|
||||
* C_PlayerGuide (Utils/C_PlayerGuide.lua) — данные и API
|
||||
* MW_PLAYER_GUIDE* (Localization/ruRU.lua) — строки
|
||||
* EncounterJournal (Custom_EncounterJournal) — родительский фрейм
|
||||
------------------------------------------------------------------------------]]
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Конфигурация фичи
|
||||
|
||||
local SHOW_REWARDS = false -- блок наград временно отключён
|
||||
local SHOW_RECOMMENDATIONS = true
|
||||
local DENSITY = "compact" -- compact | regular | cozy
|
||||
local ACCENT = { r = 1.0, g = 0.84, b = 0.42 } -- золото
|
||||
|
||||
local ROMAN = { "I","II","III","IV","V","VI","VII","VIII","IX","X" }
|
||||
|
||||
-- Биом → цвет «обложки» рекомендации (rgb 0..1).
|
||||
-- В будущем заменить на текстуры Interface\AddOns\MoonWellClient\EncounterJournal\Textures\Biome-*.
|
||||
local BIOME_COLOR = {
|
||||
fire = { 0.83, 0.29, 0.10 },
|
||||
arcane = { 0.48, 0.31, 0.83 },
|
||||
storm = { 0.24, 0.56, 0.77 },
|
||||
shadow = { 0.35, 0.16, 0.54 },
|
||||
nature = { 0.31, 0.63, 0.29 },
|
||||
holy = { 0.83, 0.69, 0.24 },
|
||||
}
|
||||
|
||||
-- Тип цели → глиф (заменить на иконки из BLP, когда будет атлас).
|
||||
local TYPE_GLYPH = {
|
||||
level = "⚔",
|
||||
quest = "!",
|
||||
quest_chain = "!",
|
||||
dungeon = "⛨",
|
||||
raid = "☠",
|
||||
boss = "☠",
|
||||
achievement = "★",
|
||||
reputation = "◈",
|
||||
profession = "⚒",
|
||||
item = "◆",
|
||||
currency = "✦",
|
||||
custom = "∗",
|
||||
}
|
||||
|
||||
-- Тип цели → плейсхолдер-иконка из встроенных текстур 3.3.5a.
|
||||
local TYPE_ICON = {
|
||||
level = "Interface\\Icons\\Ability_Warrior_Challange",
|
||||
quest = "Interface\\Icons\\INV_Misc_Note_01",
|
||||
quest_chain = "Interface\\Icons\\INV_Misc_Note_05",
|
||||
dungeon = "Interface\\Icons\\INV_Misc_Key_06",
|
||||
raid = "Interface\\Icons\\INV_Misc_Head_Dragon_01",
|
||||
boss = "Interface\\Icons\\Ability_Warrior_DecisiveStrike",
|
||||
achievement = "Interface\\Icons\\Achievement_GuildPerk_HonorableMention",
|
||||
reputation = "Interface\\Icons\\INV_Scroll_03",
|
||||
profession = "Interface\\Icons\\Trade_Engineering",
|
||||
item = "Interface\\Icons\\INV_Misc_Gem_Diamond_02",
|
||||
currency = "Interface\\Icons\\INV_Misc_Coin_17",
|
||||
custom = "Interface\\Icons\\INV_Misc_QuestionMark",
|
||||
}
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Хелперы
|
||||
|
||||
local function pct(cur, req)
|
||||
if not req or req <= 0 then return 0 end
|
||||
return math.floor((cur / req) * 100 + 0.5)
|
||||
end
|
||||
|
||||
local function setRGB(fs, c) fs:SetTextColor(c.r or c[1], c.g or c[2], c.b or c[3]) end
|
||||
|
||||
local function showOrHide(frame, show)
|
||||
if not frame then return end
|
||||
if show then frame:Show() else frame:Hide() end
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Цепочка этапов
|
||||
|
||||
local stageBadges = {} -- кэш созданных бейджей по индексу
|
||||
|
||||
local function buildStageChain(stages)
|
||||
if not PlayerGuideFrame then return end
|
||||
local container = PlayerGuideFrame.StageChain
|
||||
|
||||
-- Прячем лишние бейджи, если этапов стало меньше
|
||||
for i = (#stages + 1), #stageBadges do
|
||||
stageBadges[i]:Hide()
|
||||
end
|
||||
|
||||
if #stages == 0 then return end
|
||||
|
||||
local width = container:GetWidth()
|
||||
if width == 0 then width = 600 end
|
||||
local slot = width / #stages
|
||||
|
||||
for i, s in ipairs(stages) do
|
||||
local badge = stageBadges[i]
|
||||
if not badge then
|
||||
badge = CreateFrame("Frame", "PlayerGuideStageBadge"..i, container, "MW_PG_StageBadgeTemplate")
|
||||
stageBadges[i] = badge
|
||||
end
|
||||
badge:ClearAllPoints()
|
||||
badge:SetPoint("LEFT", container, "LEFT", (i - 0.5) * slot - 19, 0)
|
||||
badge:Show()
|
||||
|
||||
-- Текст
|
||||
badge.Roman:SetText(ROMAN[s.id] or tostring(s.id))
|
||||
badge.Label:SetText(s.short or s.name or "")
|
||||
|
||||
-- Цветовая схема по статусу
|
||||
local status = s.status or "locked"
|
||||
if status == "active" then
|
||||
badge.Roman:SetTextColor(1.0, 0.91, 0.63)
|
||||
badge.Label:SetTextColor(1.0, 0.84, 0.42)
|
||||
badge.BadgeBG:SetVertexColor(1.0, 0.84, 0.42, 1)
|
||||
elseif status == "done" then
|
||||
badge.Roman:SetTextColor(0.79, 0.65, 0.38)
|
||||
badge.Label:SetTextColor(0.79, 0.65, 0.38)
|
||||
badge.BadgeBG:SetVertexColor(0.55, 0.40, 0.18, 1)
|
||||
-- Можно заменить римскую цифру на «✓» для завершённых:
|
||||
badge.Roman:SetText("✓")
|
||||
else
|
||||
badge.Roman:SetTextColor(0.40, 0.34, 0.22)
|
||||
badge.Label:SetTextColor(0.40, 0.34, 0.22)
|
||||
badge.BadgeBG:SetVertexColor(0.20, 0.15, 0.08, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Карточки целей
|
||||
|
||||
local objectiveCards = {}
|
||||
|
||||
local CARD_HEIGHT = { compact = 44, regular = 56, cozy = 72 }
|
||||
local CARD_GAP = { compact = 4, regular = 6, cozy = 8 }
|
||||
|
||||
local function renderObjectives()
|
||||
local section = PlayerGuideFrame.ObjectivesSection
|
||||
local parent = section.ScrollFrame.ScrollChild
|
||||
local n = C_PlayerGuide.GetNumObjectives()
|
||||
|
||||
-- Скрыть лишние
|
||||
for i = (n + 1), #objectiveCards do objectiveCards[i]:Hide() end
|
||||
|
||||
local h = CARD_HEIGHT[DENSITY] or 44
|
||||
local gap = CARD_GAP[DENSITY] or 4
|
||||
local done = 0
|
||||
|
||||
for i = 1, n do
|
||||
local o = C_PlayerGuide.GetObjectiveInfo(i)
|
||||
if not o then break end
|
||||
|
||||
local card = objectiveCards[i]
|
||||
if not card then
|
||||
card = CreateFrame("Frame", "PlayerGuideObjective"..i, parent, "MW_PG_ObjectiveCardTemplate")
|
||||
objectiveCards[i] = card
|
||||
end
|
||||
card:ClearAllPoints()
|
||||
card:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, -(i - 1) * (h + gap))
|
||||
card:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, -(i - 1) * (h + gap))
|
||||
card:SetHeight(h)
|
||||
card.objectiveData = o
|
||||
card:Show()
|
||||
|
||||
-- Иконка типа
|
||||
card.Icon:SetTexture(TYPE_ICON[o.type] or TYPE_ICON.custom)
|
||||
card.Icon:SetTexCoord(0.08, 0.92, 0.08, 0.92) -- crop borders Blizz-стиль
|
||||
|
||||
-- Тип-плашка caps
|
||||
card.TypeLabel:SetText(_G["MW_PG_TYPE_"..string.upper(o.type)] or string.upper(o.type))
|
||||
|
||||
-- Заголовок и подзаголовок
|
||||
card.Title:SetText(o.title or "")
|
||||
card.Subtitle:SetText(o.subtitle or o.description or "")
|
||||
|
||||
-- Прогресс
|
||||
card.ProgressBar:SetMinMaxValues(0, o.required or 1)
|
||||
card.ProgressBar:SetValue(o.progress or 0)
|
||||
card.Count:SetText(string.format("%d / %d", o.progress or 0, o.required or 1))
|
||||
|
||||
-- Цвет по статусу
|
||||
if o.completed then
|
||||
done = done + 1
|
||||
card.Title:SetTextColor(0.62, 0.84, 0.47)
|
||||
card.TypeLabel:SetTextColor(0.49, 0.65, 0.35)
|
||||
card.Count:SetTextColor(0.62, 0.84, 0.47)
|
||||
card.ProgressBar:SetStatusBarColor(0.55, 0.85, 0.40)
|
||||
card.IconBorder:SetVertexColor(0.40, 0.65, 0.30, 0.7)
|
||||
else
|
||||
card.Title:SetTextColor(1.0, 0.91, 0.63)
|
||||
card.TypeLabel:SetTextColor(0.63, 0.46, 0.19)
|
||||
card.Count:SetTextColor(0.79, 0.65, 0.38)
|
||||
card.ProgressBar:SetStatusBarColor(ACCENT.r, ACCENT.g, ACCENT.b)
|
||||
card.IconBorder:SetVertexColor(ACCENT.r, ACCENT.g, ACCENT.b, 0.55)
|
||||
end
|
||||
end
|
||||
|
||||
parent:SetHeight(math.max(1, n * (h + gap)))
|
||||
section.Count:SetText(string.format("· %d / %d", done, n))
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Карточки рекомендаций
|
||||
|
||||
local recCards = {}
|
||||
|
||||
local function renderRecommendations()
|
||||
local section = PlayerGuideFrame.RecommendationsSection
|
||||
if not SHOW_RECOMMENDATIONS then section:Hide(); return end
|
||||
|
||||
local parent = section.ScrollFrame.ScrollChild
|
||||
local n = C_PlayerGuide.GetNumRecommendations()
|
||||
if n == 0 then section:Hide(); return end
|
||||
section:Show()
|
||||
|
||||
for i = (n + 1), #recCards do recCards[i]:Hide() end
|
||||
|
||||
local cardW = 148
|
||||
local gap = 8
|
||||
|
||||
for i = 1, n do
|
||||
local r = C_PlayerGuide.GetRecommendationInfo(i)
|
||||
if not r then break end
|
||||
|
||||
local card = recCards[i]
|
||||
if not card then
|
||||
card = CreateFrame("Button", "PlayerGuideRec"..i, parent, "MW_PG_RecCardTemplate")
|
||||
recCards[i] = card
|
||||
end
|
||||
card:ClearAllPoints()
|
||||
card:SetPoint("TOPLEFT", parent, "TOPLEFT", (i - 1) * (cardW + gap), 0)
|
||||
card.recData = r
|
||||
card:Show()
|
||||
|
||||
local biome = BIOME_COLOR[r.biome] or BIOME_COLOR.fire
|
||||
card.Artwork:SetVertexColor(biome[1], biome[2], biome[3], 1)
|
||||
card.Name:SetText(r.name or "")
|
||||
card.Loc:SetText(r.loc or "")
|
||||
card.Level:SetText(r.level or "")
|
||||
end
|
||||
|
||||
parent:SetWidth(math.max(1, n * (cardW + gap)))
|
||||
section.Count:SetText("· "..n)
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Главный Refresh
|
||||
|
||||
local function showState(state)
|
||||
local F = PlayerGuideFrame
|
||||
local hasData = (state == "data")
|
||||
|
||||
showOrHide(F.StageChain, hasData)
|
||||
showOrHide(F.Header, hasData)
|
||||
showOrHide(F.ObjectivesSection, hasData)
|
||||
showOrHide(F.RecommendationsSection, hasData and SHOW_RECOMMENDATIONS)
|
||||
showOrHide(F.EmptyState, state == "empty")
|
||||
showOrHide(F.LoadingState, state == "loading")
|
||||
end
|
||||
|
||||
local function refresh()
|
||||
if not PlayerGuideFrame then return end
|
||||
|
||||
local state = C_PlayerGuide.GetState()
|
||||
showState(state)
|
||||
|
||||
if state ~= "data" then return end
|
||||
|
||||
-- Заголовок этапа
|
||||
local id, name, desc, progress = C_PlayerGuide.GetStageInfo()
|
||||
local stages = C_PlayerGuide.GetStages() or {}
|
||||
|
||||
PlayerGuideFrame.Header.Pretitle:SetText(
|
||||
string.format(MW_PLAYER_GUIDE_STAGE_OF or "Этап %d из %d", id or 0, #stages))
|
||||
PlayerGuideFrame.Header.Title:SetText(name or "")
|
||||
PlayerGuideFrame.Header.Description:SetText(desc or "")
|
||||
PlayerGuideFrame.Header.ProgressLabel:SetText(MW_PLAYER_GUIDE_STAGE_PROGRESS or "ПРОГРЕСС ЭТАПА")
|
||||
PlayerGuideFrame.Header.ProgressPercent:SetText((progress or 0) .. "%")
|
||||
PlayerGuideFrame.Header.ProgressBar:SetMinMaxValues(0, 100)
|
||||
PlayerGuideFrame.Header.ProgressBar:SetValue(progress or 0)
|
||||
|
||||
buildStageChain(stages)
|
||||
renderObjectives()
|
||||
renderRecommendations()
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- XML script handlers
|
||||
|
||||
function MW_PlayerGuide_OnLoad(self)
|
||||
-- Подписываемся на изменения C_PlayerGuide
|
||||
C_PlayerGuide.RegisterListener(function() refresh() end)
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_OnShow(self)
|
||||
-- Если данных ещё нет — попросим сервер
|
||||
if not C_PlayerGuide.HasData() then
|
||||
C_PlayerGuide.SetLoading()
|
||||
C_PlayerGuide.RequestRefresh()
|
||||
end
|
||||
refresh()
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_RefreshButton_OnClick(self)
|
||||
C_PlayerGuide.SetLoading()
|
||||
C_PlayerGuide.RequestRefresh()
|
||||
end
|
||||
|
||||
-- Клик по карточке цели — открывает связанный таргет (см. требования)
|
||||
function MW_PlayerGuide_Objective_OnClick(self)
|
||||
local o = self.objectiveData
|
||||
if not o then return end
|
||||
|
||||
if o.type == "dungeon" or o.type == "raid" then
|
||||
if o.instanceID and EncounterJournal_OpenJournal then
|
||||
EncounterJournal_OpenJournal(o.instanceID)
|
||||
end
|
||||
elseif o.type == "boss" then
|
||||
if o.encounterID and EncounterJournal_OpenJournal then
|
||||
EncounterJournal_OpenJournal(o.instanceID, o.encounterID)
|
||||
end
|
||||
elseif o.type == "achievement" and o.achievementID then
|
||||
if AchievementFrame_LoadUI then AchievementFrame_LoadUI() end
|
||||
if AchievementFrame_SelectAchievement then
|
||||
AchievementFrame_SelectAchievement(o.achievementID)
|
||||
end
|
||||
elseif o.type == "item" and o.itemID then
|
||||
-- Запрос подсказки предмета
|
||||
if GameTooltip then
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetItemByID(o.itemID)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
else
|
||||
-- Любой нестандартный тип — пытаемся попросить сервер открыть таргет
|
||||
C_PlayerGuide.RequestOpenTarget(o.id)
|
||||
end
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_Objective_OnEnter(self)
|
||||
local o = self.objectiveData
|
||||
if not o then return end
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine(o.title or "", 1, 0.91, 0.63)
|
||||
if o.subtitle and o.subtitle ~= "" then
|
||||
GameTooltip:AddLine(o.subtitle, 0.66, 0.59, 0.47, true)
|
||||
end
|
||||
if not o.completed then
|
||||
GameTooltip:AddLine(string.format("Прогресс: %d / %d", o.progress, o.required),
|
||||
0.79, 0.65, 0.38)
|
||||
end
|
||||
GameTooltip:Show()
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_Objective_OnLeave(self) GameTooltip:Hide() end
|
||||
|
||||
function MW_PlayerGuide_Rec_OnClick(self)
|
||||
local r = self.recData
|
||||
if r and r.instanceID and EncounterJournal_OpenJournal then
|
||||
EncounterJournal_OpenJournal(r.instanceID)
|
||||
end
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_Rec_OnEnter(self)
|
||||
local r = self.recData
|
||||
if not r then return end
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine(r.name or "", 1, 0.91, 0.63)
|
||||
if r.loc then GameTooltip:AddLine(r.loc, 0.66, 0.59, 0.47) end
|
||||
if r.level then GameTooltip:AddLine("Уровень: "..r.level, 0.79, 0.65, 0.38) end
|
||||
GameTooltip:Show()
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_Rec_OnLeave(self) GameTooltip:Hide() end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Публичные хелперы для Custom_EncounterJournal.lua (routing вкладки)
|
||||
|
||||
function PlayerGuide_Show()
|
||||
if PlayerGuideFrame then
|
||||
PlayerGuideFrame:Show()
|
||||
end
|
||||
end
|
||||
|
||||
function PlayerGuide_Hide()
|
||||
if PlayerGuideFrame then
|
||||
PlayerGuideFrame:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- Глобальный экспорт для отладки
|
||||
MW_PlayerGuide = {
|
||||
Refresh = refresh,
|
||||
Show = PlayerGuide_Show,
|
||||
Hide = PlayerGuide_Hide,
|
||||
InjectDevData = function() C_PlayerGuide._InjectDevPayload() end,
|
||||
}
|
||||
@@ -0,0 +1,498 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
|
||||
<!-- ============================================================
|
||||
PlayerGuide.xml — вкладка «Путеводитель» в Encounter Journal
|
||||
Канон WotLK: тёмно-синий фон, золотые акценты, Friz Quadrata.
|
||||
Подключается ПОСЛЕ Custom_EncounterJournal.xml в .toc, чтобы
|
||||
PlayerGuideFrame мог анкориться к EncounterJournal как parent.
|
||||
============================================================ -->
|
||||
|
||||
<!-- ───────── Шрифты (Friz Quadrata из клиента 3.3.5a) ───────── -->
|
||||
|
||||
<Font name="MW_PG_Title" inherits="GameFontNormalHuge" virtual="true">
|
||||
<Color r="1.0" g="0.84" b="0.42"/>
|
||||
<Shadow><Color r="0" g="0" b="0"/><Offset><AbsDimension x="1" y="-1"/></Offset></Shadow>
|
||||
</Font>
|
||||
|
||||
<Font name="MW_PG_Section" inherits="QuestFont_Large" virtual="true">
|
||||
<Color r="1.0" g="0.84" b="0.42"/>
|
||||
<Shadow><Color r="0" g="0" b="0"/><Offset><AbsDimension x="1" y="-1"/></Offset></Shadow>
|
||||
</Font>
|
||||
|
||||
<Font name="MW_PG_Body" inherits="GameFontHighlight" virtual="true">
|
||||
<Color r="0.85" g="0.78" b="0.63"/>
|
||||
</Font>
|
||||
|
||||
<Font name="MW_PG_Caps" inherits="GameFontNormalSmall" virtual="true">
|
||||
<Color r="0.63" g="0.46" b="0.19"/>
|
||||
</Font>
|
||||
|
||||
<Font name="MW_PG_Italic" inherits="QuestFont" virtual="true">
|
||||
<Color r="0.66" g="0.59" b="0.47"/>
|
||||
</Font>
|
||||
|
||||
<!-- ───────── Backdrop'ы ───────── -->
|
||||
|
||||
<!-- Карточка цели (полупрозрачная пергамент-плашка) -->
|
||||
<Frame name="MW_PG_ObjectiveCardTemplate" virtual="true">
|
||||
<Size><AbsDimension x="0" y="44"/></Size>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background"
|
||||
edgeFile="Interface\Tooltips\UI-Tooltip-Border"
|
||||
tile="true">
|
||||
<BackgroundInsets><AbsInset left="3" right="3" top="3" bottom="3"/></BackgroundInsets>
|
||||
<TileSize><AbsValue val="16"/></TileSize>
|
||||
<EdgeSize><AbsValue val="12"/></EdgeSize>
|
||||
<Color r="0.22" g="0.15" b="0.07" a="0.65"/>
|
||||
<BorderColor r="0.55" g="0.40" b="0.18" a="1.0"/>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<!-- Иконка типа цели (32×32, золотая рамка применяется отдельной текстурой) -->
|
||||
<Texture parentKey="Icon" file="Interface\Icons\INV_Misc_QuestionMark">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture parentKey="IconBorder" file="Interface\Buttons\UI-Quickslot2">
|
||||
<Size><AbsDimension x="42" y="42"/></Size>
|
||||
<Anchors><Anchor point="CENTER" relativeKey="$parent.Icon"/></Anchors>
|
||||
<TexCoords left="0.2" right="0.8" top="0.2" bottom="0.8"/>
|
||||
<Color r="1.0" g="0.84" b="0.42" a="0.55"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="TypeLabel" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Size><AbsDimension x="100" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Icon" relativePoint="TOPRIGHT">
|
||||
<Offset><AbsDimension x="10" y="-1"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Title" inherits="GameFontNormal" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.TypeLabel" relativePoint="TOPRIGHT">
|
||||
<Offset><AbsDimension x="6" y="0"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-90" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<FontString parentKey="Subtitle" inherits="MW_PG_Italic" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.TypeLabel" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="0" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-90" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Count" inherits="GameFontNormalSmall" justifyH="RIGHT">
|
||||
<Size><AbsDimension x="80" y="12"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-10" y="-6"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="0.79" g="0.65" b="0.38"/>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
|
||||
<Frames>
|
||||
<StatusBar parentKey="ProgressBar">
|
||||
<Size><AbsDimension x="0" y="6"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeKey="$parent.Icon" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="10" y="-12"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-10" y="-12"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||||
<BarColor r="1.0" g="0.79" b="0.29"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture setAllPoints="true" file="Interface\Buttons\WHITE8X8">
|
||||
<Color r="0.05" g="0.04" b="0.02" a="0.85"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</StatusBar>
|
||||
</Frames>
|
||||
|
||||
<Scripts>
|
||||
<OnClick function="MW_PlayerGuide_Objective_OnClick"/>
|
||||
<OnEnter function="MW_PlayerGuide_Objective_OnEnter"/>
|
||||
<OnLeave function="MW_PlayerGuide_Objective_OnLeave"/>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
<!-- Карточка рекомендованного подземелья -->
|
||||
<Button name="MW_PG_RecCardTemplate" virtual="true">
|
||||
<Size><AbsDimension x="148" y="106"/></Size>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background"
|
||||
edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
|
||||
<BackgroundInsets><AbsInset left="3" right="3" top="3" bottom="3"/></BackgroundInsets>
|
||||
<TileSize><AbsValue val="16"/></TileSize>
|
||||
<EdgeSize><AbsValue val="12"/></EdgeSize>
|
||||
<Color r="0.10" g="0.07" b="0.03" a="0.85"/>
|
||||
<BorderColor r="0.55" g="0.40" b="0.18" a="1.0"/>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<!-- Биом-плашка (плейсхолдер: цветной градиент) -->
|
||||
<Texture parentKey="Artwork" file="Interface\Buttons\WHITE8X8">
|
||||
<Size><AbsDimension x="138" y="58"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="0.55" g="0.16" b="0.07"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="Level" inherits="GameFontNormalSmall" justifyH="CENTER">
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.Artwork" relativePoint="TOPRIGHT">
|
||||
<Offset><AbsDimension x="-4" y="-4"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<FontString parentKey="Name" inherits="GameFontNormal" justifyH="LEFT">
|
||||
<Size><AbsDimension x="130" y="12"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Artwork" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="2" y="-4"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<FontString parentKey="Loc" inherits="MW_PG_Italic" justifyH="LEFT">
|
||||
<Size><AbsDimension x="130" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Name" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="0" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnClick function="MW_PlayerGuide_Rec_OnClick"/>
|
||||
<OnEnter function="MW_PlayerGuide_Rec_OnEnter"/>
|
||||
<OnLeave function="MW_PlayerGuide_Rec_OnLeave"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Бейдж этапа в цепочке (круглая золотая монета с цифрой) -->
|
||||
<Frame name="MW_PG_StageBadgeTemplate" virtual="true">
|
||||
<Size><AbsDimension x="38" y="38"/></Size>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture parentKey="BadgeBG" file="Interface\Buttons\UI-Quickslot2">
|
||||
<Size><AbsDimension x="60" y="60"/></Size>
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
<TexCoords left="0.2" right="0.8" top="0.2" bottom="0.8"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="Roman" inherits="GameFontNormalLarge" justifyH="CENTER">
|
||||
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<FontString parentKey="Label" inherits="MW_PG_Caps" justifyH="CENTER">
|
||||
<Size><AbsDimension x="100" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
|
||||
<!-- ───────── Корневой фрейм ───────── -->
|
||||
<!-- Парент: EncounterJournal.encounter (см. PlayerGuide.lua → Init) -->
|
||||
|
||||
<Frame name="PlayerGuideFrame" hidden="true" parent="EncounterJournal">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="22" y="-58"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-6" y="6"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
|
||||
<Layers>
|
||||
<!-- «Волна» сверху, как в оригинальном EJ — добавляется через
|
||||
родительский фон. Здесь — тонкая золотая внутренняя рамка. -->
|
||||
<Layer level="BORDER">
|
||||
<Texture parentKey="TopGlow" file="Interface\Common\UI-TooltipDivider-Transparent">
|
||||
<Size><AbsDimension x="0" y="16"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-58"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="0" y="-58"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="0.84" b="0.42" a="0.35"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
|
||||
<Frames>
|
||||
|
||||
<!-- 1) Цепочка этапов сверху -->
|
||||
<Frame parentKey="StageChain">
|
||||
<Size><AbsDimension x="0" y="62"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-4"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-8" y="-4"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<!-- Бейджи создаются программно в Lua (PlayerGuide_BuildStageChain) -->
|
||||
</Frame>
|
||||
|
||||
<!-- 2) Заголовок текущего этапа -->
|
||||
<Frame parentKey="Header">
|
||||
<Size><AbsDimension x="0" y="78"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.StageChain" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="12" y="-12"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.StageChain" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="-12" y="-12"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="Pretitle" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Title" inherits="MW_PG_Title" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Pretitle" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="0" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-260" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Description" inherits="MW_PG_Body" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Title" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="2" y="-6"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-260" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
|
||||
<FontString parentKey="ProgressLabel" inherits="MW_PG_Caps" justifyH="RIGHT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="ProgressPercent" inherits="MW_PG_Title" justifyH="RIGHT">
|
||||
<Size><AbsDimension x="240" y="28"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.ProgressLabel" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="0" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<StatusBar parentKey="ProgressBar">
|
||||
<Size><AbsDimension x="240" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.ProgressPercent" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="0" y="-4"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||||
<BarColor r="1.0" g="0.79" b="0.29"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture setAllPoints="true" file="Interface\Buttons\WHITE8X8">
|
||||
<Color r="0.05" g="0.04" b="0.02" a="0.85"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</StatusBar>
|
||||
|
||||
<Button parentKey="RefreshButton" inherits="UIPanelButtonTemplate2" text="MW_PLAYER_GUIDE_REFRESH">
|
||||
<Size><AbsDimension x="100" y="22"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.ProgressBar" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="0" y="-6"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick function="MW_PlayerGuide_RefreshButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 3) Секция «Задачи этапа» — заголовок + список карточек -->
|
||||
<Frame parentKey="ObjectivesSection">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Header" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="-2" y="-10"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.Header" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="2" y="-10"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="142"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="Heading" inherits="MW_PG_Section" justifyH="LEFT" text="MW_PLAYER_GUIDE_OBJECTIVES">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Count" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeKey="$parent.Heading" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="8" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<Texture parentKey="Divider" file="Interface\Common\UI-TooltipDivider-Transparent">
|
||||
<Size><AbsDimension x="0" y="2"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeKey="$parent.Heading" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="0" y="-4"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<ScrollFrame parentKey="ScrollFrame" inherits="UIPanelScrollFrameTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="4" y="-26"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-26" y="4"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<ScrollChild>
|
||||
<Frame parentKey="ScrollChild">
|
||||
<Size><AbsDimension x="600" y="200"/></Size>
|
||||
<!-- Карточки целей вставляются программно через ObjectiveCardTemplate -->
|
||||
</Frame>
|
||||
</ScrollChild>
|
||||
</ScrollFrame>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 4) Секция «Подходящие подземелья» — горизонтальная карусель -->
|
||||
<Frame parentKey="RecommendationsSection">
|
||||
<Size><AbsDimension x="0" y="132"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="6" y="6"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-6" y="6"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="Heading" inherits="MW_PG_Section" justifyH="LEFT" text="MW_PLAYER_GUIDE_RECS">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Count" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeKey="$parent.Heading" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="8" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<Texture parentKey="Divider" file="Interface\Common\UI-TooltipDivider-Transparent">
|
||||
<Size><AbsDimension x="0" y="2"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeKey="$parent.Heading" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="0" y="-4"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<ScrollFrame parentKey="ScrollFrame" inherits="UIPanelScrollFrameTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="4" y="-26"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-26" y="4"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<ScrollChild>
|
||||
<Frame parentKey="ScrollChild">
|
||||
<Size><AbsDimension x="900" y="110"/></Size>
|
||||
</Frame>
|
||||
</ScrollChild>
|
||||
</ScrollFrame>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 5) Пустое состояние -->
|
||||
<Frame parentKey="EmptyState" hidden="true">
|
||||
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="20"/></Offset></Anchor></Anchors>
|
||||
<Size><AbsDimension x="460" y="200"/></Size>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="Title" inherits="MW_PG_Title" justifyH="CENTER" text="MW_PLAYER_GUIDE_EMPTY_TITLE">
|
||||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="-20"/></Offset></Anchor></Anchors>
|
||||
<Size><AbsDimension x="460" y="28"/></Size>
|
||||
</FontString>
|
||||
<FontString parentKey="Body" inherits="MW_PG_Body" justifyH="CENTER" text="MW_PLAYER_GUIDE_EMPTY_BODY">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeKey="$parent.Title" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="0" y="-10"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Size><AbsDimension x="420" y="60"/></Size>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button parentKey="RefreshButton" inherits="UIPanelButtonTemplate2" text="MW_PLAYER_GUIDE_REFRESH">
|
||||
<Size><AbsDimension x="120" y="24"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeKey="$parent.Body" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="0" y="-14"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick function="MW_PlayerGuide_RefreshButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 6) Состояние загрузки -->
|
||||
<Frame parentKey="LoadingState" hidden="true">
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
<Size><AbsDimension x="300" y="80"/></Size>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="Title" inherits="MW_PG_Section" justifyH="CENTER" text="MW_PLAYER_GUIDE_LOADING_TITLE">
|
||||
<Anchors><Anchor point="TOP"/></Anchors>
|
||||
<Size><AbsDimension x="300" y="20"/></Size>
|
||||
</FontString>
|
||||
<FontString parentKey="Body" inherits="MW_PG_Italic" justifyH="CENTER" text="MW_PLAYER_GUIDE_LOADING_BODY">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeKey="$parent.Title" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="0" y="-6"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Size><AbsDimension x="300" y="16"/></Size>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
|
||||
</Frames>
|
||||
|
||||
<Scripts>
|
||||
<OnLoad function="MW_PlayerGuide_OnLoad"/>
|
||||
<OnShow function="MW_PlayerGuide_OnShow"/>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
</Ui>
|
||||
@@ -0,0 +1,64 @@
|
||||
# PlayerGuide — клиентский пакет (3.3.5a / MoonWellClient)
|
||||
|
||||
Это материалы для интеграции вкладки **«Путеводитель»** в существующий
|
||||
ретропортированный Encounter Journal. Реализация соответствует требованиям
|
||||
из `uploads/player-guide-requirements.md` и опирается на канон-визуал
|
||||
WotLK (Friz Quadrata, тёмно-синий фон EJ, золотые акценты).
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
MoonWellClient/
|
||||
├── EncounterJournal/
|
||||
│ ├── Utils/
|
||||
│ │ └── C_PlayerGuide.lua ← хранилище + API
|
||||
│ ├── Custom_EncounterJournal/
|
||||
│ │ ├── Custom_EncounterJournal.xml ← патч: новая вкладка
|
||||
│ │ ├── Custom_EncounterJournal.lua ← патч: routing вкладки
|
||||
│ │ ├── PlayerGuide.xml ← фреймы и шаблоны (НОВОЕ)
|
||||
│ │ └── PlayerGuide.lua ← рендер и логика (НОВОЕ)
|
||||
│ └── Localization/
|
||||
│ └── ruRU.lua ← патч: MW_PLAYER_GUIDE и др.
|
||||
├── AIO/
|
||||
│ └── MoonWellPlayerGuide.lua ← AIO-handler (клиентская часть)
|
||||
└── MoonWellClient.toc ← патч: подключение файлов
|
||||
```
|
||||
|
||||
## Порядок интеграции
|
||||
|
||||
1. **Скопируйте новые файлы** в проект:
|
||||
* `client-export/PlayerGuide.xml` → `EncounterJournal/Custom_EncounterJournal/PlayerGuide.xml`
|
||||
* `client-export/PlayerGuide.lua` → `EncounterJournal/Custom_EncounterJournal/PlayerGuide.lua`
|
||||
* `client-export/C_PlayerGuide.lua` → `EncounterJournal/Utils/C_PlayerGuide.lua`
|
||||
* `client-export/MoonWellPlayerGuide.lua` → `AIO/MoonWellPlayerGuide.lua`
|
||||
2. **Примените патчи** из `client-export/patches/` к существующим файлам
|
||||
(см. ниже — `Custom_EncounterJournal.xml`, `Custom_EncounterJournal.lua`,
|
||||
`MoonWellClient.toc`, `Localization/ruRU.lua`).
|
||||
3. **Перезагрузите интерфейс** (`/reload`). Если AIO ещё не передал
|
||||
payload — увидите «пустое» состояние с кнопкой «Обновить».
|
||||
|
||||
## Что вошло в MVP (по требованиям)
|
||||
|
||||
| MVP-пункт | Где |
|
||||
| ----------------------------- | ------------------------------------------- |
|
||||
| вкладка `Путеводитель` | `Custom_EncounterJournal.xml` патч |
|
||||
| пустой `PlayerGuideFrame` | `PlayerGuide.xml` |
|
||||
| `C_PlayerGuide.lua` + статика | `C_PlayerGuide.lua` |
|
||||
| заголовок этапа + цели | `PlayerGuide.lua → PlayerGuide_Refresh` |
|
||||
| tab switching / hide-show | `Custom_EncounterJournal.lua → EJ_ContentTab_OnClick` хук |
|
||||
| AIO `SetData` | `MoonWellPlayerGuide.lua → handlers.SetData` |
|
||||
| Обновить (RequestRefresh) | `PlayerGuide.lua → PlayerGuide_RefreshButton_OnClick` |
|
||||
|
||||
## Что отложено (см. «Later UI» в требованиях)
|
||||
|
||||
* Фильтры (все / активные / завершённые)
|
||||
* Pinning отслеживаемой цели
|
||||
* **Награды** — блок временно отключён (`SHOW_REWARDS = false`)
|
||||
* Серверные уведомления (`Notify`)
|
||||
|
||||
## Сноска по визуалу
|
||||
|
||||
Текстуры в XML — это встроенные ресурсы 3.3.5a-клиента. Если у вас
|
||||
есть атлас MoonWell — замените пути в блоке `-- TEXTURE_ATLAS` на свои.
|
||||
Биом-обложки рекомендаций (`Interface\AddOns\MoonWellClient\EncounterJournal\Textures\Biome-*`)
|
||||
надо создать отдельно — это плейсхолдеры (см. таблицу в `PlayerGuide.lua`).
|
||||
@@ -0,0 +1,120 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
patches/Custom_EncounterJournal.lua.patch
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
Патч-сниппеты для существующего Custom_EncounterJournal.lua. Эти куски
|
||||
добавляют routing новой вкладки и hide-show поведение по требованиям:
|
||||
|
||||
«при выборе guide tab:
|
||||
- hide instance cards
|
||||
- hide Encounter Journal detail panel
|
||||
- hide suggested content panel
|
||||
- hide loot journal panel
|
||||
- hide or disable the expansion/tier dropdown unless explicitly needed
|
||||
- show PlayerGuideFrame»
|
||||
------------------------------------------------------------------------------]]
|
||||
|
||||
-- ─── ПАТЧ #1 ───────────────────────────────────────────────────────────────
|
||||
-- НАЙТИ функцию EJ_ContentTab_Select (или ту, что переключает вкладки в
|
||||
-- Encounter Journal). Обычно она выглядит как switch по id вкладки.
|
||||
-- ДОБАВИТЬ ветку для нашей вкладки. Пример полностью переписанной функции:
|
||||
|
||||
local PLAYERGUIDE_TAB_ID = 1 -- должен совпадать с id="1" в XML патче
|
||||
|
||||
function EJ_ContentTab_Select(id)
|
||||
EncounterJournal.suggestTab:UnlockHighlight()
|
||||
EncounterJournal.dungeonsTab:UnlockHighlight()
|
||||
EncounterJournal.raidsTab:UnlockHighlight()
|
||||
if EncounterJournal.lootJournalTab then
|
||||
EncounterJournal.lootJournalTab:UnlockHighlight()
|
||||
end
|
||||
if EncounterJournal.guideTab then
|
||||
EncounterJournal.guideTab:UnlockHighlight()
|
||||
end
|
||||
|
||||
-- Прячем «обычные» панели EJ. Этот хелпер уже есть в Custom_EncounterJournal:
|
||||
-- EncounterJournal_HideAllPanels()
|
||||
-- Если его нет — выньте тело сюда (Hide instance list, encounter, loot).
|
||||
EncounterJournal_HideAllPanels()
|
||||
|
||||
-- Прячем PlayerGuideFrame — он показывается только на guide-вкладке.
|
||||
if PlayerGuideFrame then PlayerGuideFrame:Hide() end
|
||||
|
||||
if id == PLAYERGUIDE_TAB_ID then
|
||||
-- НАША ВКЛАДКА -----------------------------------------------------
|
||||
EncounterJournal.guideTab:LockHighlight()
|
||||
EncounterJournal.instanceSelect:Hide()
|
||||
EncounterJournal.encounter:Hide()
|
||||
if EncounterJournalSuggestFrame then EncounterJournalSuggestFrame:Hide() end
|
||||
if LootJournal then LootJournal:Hide() end
|
||||
-- Скрыть/задисейблить дропдаун расширений, как просят требования:
|
||||
if EJTierDropDown then EJTierDropDown:Hide() end
|
||||
|
||||
if PlayerGuideFrame then PlayerGuideFrame:Show() end
|
||||
return
|
||||
end
|
||||
|
||||
-- ── Остальные вкладки — оригинальная логика ──
|
||||
if EJTierDropDown then EJTierDropDown:Show() end
|
||||
|
||||
if id == 2 then -- SuggestTab (было 1)
|
||||
EncounterJournal.suggestTab:LockHighlight()
|
||||
EncounterJournal_ListInstances() -- или ваш Suggest-Show
|
||||
elseif id == 3 then -- DungeonTab
|
||||
EncounterJournal.dungeonsTab:LockHighlight()
|
||||
EJ_HideNonInstancePanels()
|
||||
EncounterJournal_ListInstances()
|
||||
elseif id == 4 then -- RaidTab
|
||||
EncounterJournal.raidsTab:LockHighlight()
|
||||
EJ_HideNonInstancePanels()
|
||||
EncounterJournal_ListInstances()
|
||||
elseif id == 5 then -- LootJournalTab
|
||||
if EncounterJournal.lootJournalTab then
|
||||
EncounterJournal.lootJournalTab:LockHighlight()
|
||||
end
|
||||
if LootJournal_Show then LootJournal_Show() end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- ─── ПАТЧ #2 ───────────────────────────────────────────────────────────────
|
||||
-- В EJ_ContentTab_OnClick добавить вызов нашего Select для tab id=1
|
||||
-- (если у вас один централизованный _OnClick — он уже передаёт id и
|
||||
-- ничего дополнительно делать не нужно):
|
||||
|
||||
function EJ_ContentTab_OnClick(self)
|
||||
EJ_ContentTab_Select(self:GetID())
|
||||
PlaySound("igCharacterInfoTab")
|
||||
end
|
||||
|
||||
|
||||
-- ─── ПАТЧ #3 ───────────────────────────────────────────────────────────────
|
||||
-- Защита: при открытии EJ через EncounterJournal_OpenJournal — переключиться
|
||||
-- НА dungeon/raid таб, а не оставлять guide-таб выбранным.
|
||||
-- Найдите вашу EncounterJournal_OpenJournal и убедитесь, что в начале:
|
||||
|
||||
local _orig_EncounterJournal_OpenJournal = EncounterJournal_OpenJournal
|
||||
function EncounterJournal_OpenJournal(instanceID, encounterID, ...)
|
||||
-- При прямом открытии конкретного instance — переключиться на Dungeon/Raid
|
||||
if instanceID then
|
||||
EJ_ContentTab_Select(3) -- Dungeon по умолчанию; ваша логика может выбрать Raid
|
||||
end
|
||||
return _orig_EncounterJournal_OpenJournal(instanceID, encounterID, ...)
|
||||
end
|
||||
|
||||
|
||||
-- ─── ПАТЧ #4 ───────────────────────────────────────────────────────────────
|
||||
-- Init: статика для разработки (можно убрать перед релизом). Если AIO
|
||||
-- ещё не успел прислать payload — покажем dev-payload через 1с после
|
||||
-- первого открытия EJ.
|
||||
|
||||
local _devInjected = false
|
||||
EncounterJournal:HookScript("OnShow", function()
|
||||
if not _devInjected and not C_PlayerGuide.HasData() then
|
||||
_devInjected = true
|
||||
C_Timer.After(1.0, function()
|
||||
if not C_PlayerGuide.HasData() then
|
||||
C_PlayerGuide._InjectDevPayload()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,57 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
patches/Custom_EncounterJournal.xml.patch
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
Это НЕ замена файла — это патч-сниппеты для существующего
|
||||
Custom_EncounterJournal.xml. Вставьте обозначенные блоки.
|
||||
|
||||
В оригинальном Custom_EncounterJournal.xml внутри EncounterJournal.bg или
|
||||
аналогичного контейнера уже есть вкладки SuggestTab / DungeonTab / RaidTab /
|
||||
LootJournalTab. Нужно вставить GuideTab ПЕРЕД ними (id=1) и сдвинуть
|
||||
анкоры остальных.
|
||||
------------------------------------------------------------------------------]]
|
||||
|
||||
-- ─── ПАТЧ #1 ───────────────────────────────────────────────────────────────
|
||||
-- НАЙТИ блок вкладки SuggestTab (или то, что у вас сейчас называется
|
||||
-- «$parentSuggestTab» / «$parentDungeonTab») и ДОБАВИТЬ перед ним
|
||||
-- следующую кнопку. id=1, чтобы EJ_ContentTab_OnClick знал об этой вкладке.
|
||||
|
||||
<Button name="$parentGuideTab"
|
||||
inherits="EncounterTierTabTemplate"
|
||||
text="MW_PLAYER_GUIDE"
|
||||
parentKey="guideTab"
|
||||
id="1">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="$parent" relativePoint="TOPLEFT">
|
||||
<Offset><AbsDimension x="40" y="2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick function="EJ_ContentTab_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
|
||||
-- ─── ПАТЧ #2 ───────────────────────────────────────────────────────────────
|
||||
-- ИЗМЕНИТЬ id существующих вкладок (теперь Путеводитель = 1):
|
||||
--
|
||||
-- SuggestTab id="2" (было 1)
|
||||
-- DungeonTab id="3" (было 2)
|
||||
-- RaidTab id="4" (было 3)
|
||||
-- LootJournalTab id="5" (было 4)
|
||||
--
|
||||
-- И СДВИНУТЬ анкор первой следующей вкладки (SuggestTab), чтобы она
|
||||
-- цеплялась за GuideTab:
|
||||
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="$parentGuideTab" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="-15" y="0"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
|
||||
|
||||
-- ─── ПАТЧ #3 ───────────────────────────────────────────────────────────────
|
||||
-- В конце <Ui>... </Ui>, ПЕРЕД закрывающим тегом, добавить include
|
||||
-- для нашего PlayerGuide.xml, чтобы PlayerGuideFrame создавался ВНУТРИ
|
||||
-- родителя EncounterJournal:
|
||||
|
||||
<Include file="PlayerGuide.xml"/>
|
||||
@@ -0,0 +1,43 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
patches/Localization-ruRU.lua.patch
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
Строки локализации для вкладки «Путеводитель».
|
||||
Вставьте этот блок в ваш существующий Localization/ruRU.lua (или
|
||||
ruRU.lua в корне), либо подключите как отдельный файл и добавьте в .toc.
|
||||
------------------------------------------------------------------------------]]
|
||||
|
||||
-- Имя вкладки
|
||||
MW_PLAYER_GUIDE = "Путеводитель"
|
||||
|
||||
-- Заголовки секций
|
||||
MW_PLAYER_GUIDE_OBJECTIVES = "Задачи этапа"
|
||||
MW_PLAYER_GUIDE_RECS = "Подходящие подземелья"
|
||||
MW_PLAYER_GUIDE_REWARDS = "Награды за этап"
|
||||
|
||||
-- Шапка
|
||||
MW_PLAYER_GUIDE_STAGE_OF = "ЭТАП %d ИЗ %d"
|
||||
MW_PLAYER_GUIDE_STAGE_PROGRESS = "ПРОГРЕСС ЭТАПА"
|
||||
MW_PLAYER_GUIDE_REFRESH = "Обновить"
|
||||
|
||||
-- Пустое состояние
|
||||
MW_PLAYER_GUIDE_EMPTY_TITLE = "Путеводитель ещё не открыт"
|
||||
MW_PLAYER_GUIDE_EMPTY_BODY = "Данные о вашем прогрессе ещё не получены с сервера. "..
|
||||
"Это может занять несколько секунд после входа в игру."
|
||||
|
||||
-- Загрузка
|
||||
MW_PLAYER_GUIDE_LOADING_TITLE = "ЗАПРАШИВАЕМ ХРОНИКИ"
|
||||
MW_PLAYER_GUIDE_LOADING_BODY = "Ожидание данных от Путеводителя"
|
||||
|
||||
-- Caps-плашки типов целей (используются в карточке цели)
|
||||
MW_PG_TYPE_LEVEL = "УРОВЕНЬ"
|
||||
MW_PG_TYPE_QUEST = "ЗАДАНИЕ"
|
||||
MW_PG_TYPE_QUEST_CHAIN = "ЦЕПОЧКА"
|
||||
MW_PG_TYPE_DUNGEON = "ПОДЗЕМЕЛЬЕ"
|
||||
MW_PG_TYPE_RAID = "РЕЙД"
|
||||
MW_PG_TYPE_BOSS = "БОСС"
|
||||
MW_PG_TYPE_ACHIEVEMENT = "ДОСТИЖЕНИЕ"
|
||||
MW_PG_TYPE_REPUTATION = "РЕПУТАЦИЯ"
|
||||
MW_PG_TYPE_PROFESSION = "ПРОФЕССИЯ"
|
||||
MW_PG_TYPE_ITEM = "ПРЕДМЕТ"
|
||||
MW_PG_TYPE_CURRENCY = "ВАЛЮТА"
|
||||
MW_PG_TYPE_CUSTOM = "ЗАДАЧА"
|
||||
@@ -0,0 +1,28 @@
|
||||
# patches/MoonWellClient.toc.patch
|
||||
#
|
||||
# Сниппет для добавления в существующий MoonWellClient.toc. ВАЖНО: порядок
|
||||
# подключения файлов критичен — C_PlayerGuide.lua должен идти ДО PlayerGuide.lua,
|
||||
# а PlayerGuide.xml — ПОСЛЕ Custom_EncounterJournal.xml (PlayerGuideFrame
|
||||
# анкорится к EncounterJournal как parent).
|
||||
#
|
||||
# Вставьте строки ниже в соответствующие места вашего .toc.
|
||||
|
||||
# ─── Локализация ────────────────────────────────────────────────────────────
|
||||
# В блок Localization, рядом с остальными ruRU.lua:
|
||||
Localization\ruRU.lua
|
||||
|
||||
# ─── Хранилище и API ────────────────────────────────────────────────────────
|
||||
# В блок EncounterJournal\Utils\, до подключения чего-либо из
|
||||
# Custom_EncounterJournal\:
|
||||
EncounterJournal\Utils\C_PlayerGuide.lua
|
||||
|
||||
# ─── Сам Encounter Journal (ОБНОВЛЁННЫЙ ПОРЯДОК) ────────────────────────────
|
||||
# Эти строки заменяют ваш существующий блок Custom_EncounterJournal:
|
||||
EncounterJournal\Custom_EncounterJournal\Custom_EncounterJournal.xml
|
||||
EncounterJournal\Custom_EncounterJournal\PlayerGuide.xml
|
||||
EncounterJournal\Custom_EncounterJournal\PlayerGuide.lua
|
||||
EncounterJournal\Custom_EncounterJournal\Custom_EncounterJournal.lua
|
||||
|
||||
# ─── AIO-handler ────────────────────────────────────────────────────────────
|
||||
# Должен подключаться ПОСЛЕ C_PlayerGuide.lua и AIO/AIO_Client.lua:
|
||||
AIO\MoonWellPlayerGuide.lua
|
||||
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1015 B |
|
After Width: | Height: | Size: 1015 B |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 394 B |
|
After Width: | Height: | Size: 307 B |
@@ -0,0 +1,125 @@
|
||||
# PlayerGuide — текстуры
|
||||
|
||||
Все файлы — **PNG с альфа-каналом**, размеры — **степень двойки**
|
||||
(требование BLP-конвертера). Палитра канон-WotLK: золото (`#ffd76a`)
|
||||
на тёмно-коричневом (`#1a1208`).
|
||||
|
||||
## Что есть
|
||||
|
||||
| Файл | Размер | Куда |
|
||||
|---|---|---|
|
||||
| `StageBadge-Active.png` | 64×64 | Бейдж текущего этапа в цепочке. |
|
||||
| `StageBadge-Done.png` | 64×64 | Бейдж завершённого этапа. |
|
||||
| `StageBadge-Locked.png` | 64×64 | Бейдж недоступного этапа. |
|
||||
| `ProgressBar-Fill.png` | 128×16 | Заливка прогресс-бара (`BarTexture`). |
|
||||
| `ProgressBar-BG.png` | 128×16 | Подложка прогресс-бара. |
|
||||
| `Card-Background.png` | 256×256 | Тайл-фон карточек (`bgFile`, tileable). |
|
||||
| `Card-Border.png` | 256×32 | Рамка карточки (`edgeFile`, 8 сегментов 32×32: up/down/left/right/TL/TR/BL/BR). |
|
||||
| `Section-Divider.png` | 256×16 | Декоративный разделитель секций. |
|
||||
| `Corner-Ornament.png` | 64×64 | Угол декоративной рамки фрейма. |
|
||||
| `Top-Wave-Glow.png` | 512×64 | «Волна» свечения сверху (как в оригинальном EJ). |
|
||||
| `Biome-Fire.png` | 256×128 | Обложка рекомендации — огненные локации. |
|
||||
| `Biome-Arcane.png` | 256×128 | Тайная магия. |
|
||||
| `Biome-Storm.png` | 256×128 | Буря/вода. |
|
||||
| `Biome-Shadow.png` | 256×128 | Тьма. |
|
||||
| `Biome-Nature.png` | 256×128 | Природа. |
|
||||
| `Biome-Holy.png` | 256×128 | Свет/нежить. |
|
||||
| `TypeIcon-Level.png` | 64×64 | Иконка цели «уровень». |
|
||||
| `TypeIcon-Quest.png` | 64×64 | «задание». |
|
||||
| `TypeIcon-QuestChain.png` | 64×64 | «цепочка». |
|
||||
| `TypeIcon-Dungeon.png` | 64×64 | «подземелье». |
|
||||
| `TypeIcon-Raid.png` | 64×64 | «рейд». |
|
||||
| `TypeIcon-Boss.png` | 64×64 | «босс». |
|
||||
| `TypeIcon-Achievement.png` | 64×64 | «достижение». |
|
||||
| `TypeIcon-Reputation.png` | 64×64 | «репутация». |
|
||||
| `TypeIcon-Profession.png` | 64×64 | «профессия». |
|
||||
| `TypeIcon-Item.png` | 64×64 | «предмет». |
|
||||
| `TypeIcon-Currency.png` | 64×64 | «валюта». |
|
||||
| `TypeIcon-Custom.png` | 64×64 | прочее. |
|
||||
| `TypeIcon-Done.png` | 64×64 | Цель завершена (зелёная галочка). |
|
||||
| `TypeIcon-Locked.png` | 64×64 | Цель заблокирована (замок). |
|
||||
| `Button-Up.png` | 128×32 | Кнопка «Обновить», нормальное. |
|
||||
| `Button-Down.png` | 128×32 | Кнопка нажата. |
|
||||
| `Button-Highlight.png` | 128×32 | Hover. |
|
||||
|
||||
## Куда положить в проекте
|
||||
|
||||
```
|
||||
MoonWellClient/EncounterJournal/Textures/PlayerGuide/
|
||||
├── StageBadge-Active.blp
|
||||
├── StageBadge-Done.blp
|
||||
├── ...
|
||||
└── Button-Highlight.blp
|
||||
```
|
||||
|
||||
И в XML использовать пути:
|
||||
```
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\StageBadge-Active"
|
||||
```
|
||||
(WoW сам подставит `.blp`.)
|
||||
|
||||
## Конвертация PNG → BLP
|
||||
|
||||
Любой из:
|
||||
- **BLPNG Converter** (Windows, GUI) — самый простой, drag-and-drop;
|
||||
- **BLPLab** — с предпросмотром;
|
||||
- **CLI**: `blpconverter input.png output.blp` (есть пайплайны на GitHub).
|
||||
|
||||
Параметры:
|
||||
- **Compression:** `DXT5` для всего с альфой (всё, кроме `Card-Background` —
|
||||
для него `DXT1` или `Uncompressed`, если хочется лучше);
|
||||
- **Mipmaps:** включить (`Generate Mipmaps`), кроме `ProgressBar-Fill/BG`
|
||||
(тонкие полоски ломаются на мипмапах — там лучше off);
|
||||
- **Sampling:** `Bicubic` или `Lanczos`.
|
||||
|
||||
## Замена биом-обложек
|
||||
|
||||
Сейчас биом-обложки — стилизованные плашки с аркой и светящимся центром.
|
||||
Их можно полностью заменить вашими собственными — главное, оставить размер
|
||||
**256×128** и имена файлов. Если поменяете список биомов, поправьте таблицу
|
||||
`BIOME_COLOR` в `PlayerGuide.lua`.
|
||||
|
||||
## Замена иконок типов целей
|
||||
|
||||
Если у вас есть атлас иконок WoW (например, из `Interface/Icons/`) — карта
|
||||
`TYPE_ICON` в `PlayerGuide.lua` уже использует встроенные текстуры
|
||||
клиента. Текстуры `TypeIcon-*` нужны, только если хочется единого
|
||||
визуального словаря (рамка + глиф).
|
||||
|
||||
## Применение в XML (примеры)
|
||||
|
||||
```xml
|
||||
<!-- Бейдж в цепочке этапов: меняем встроенный UI-Quickslot2 на наш -->
|
||||
<Texture parentKey="BadgeBG"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\StageBadge-Active">
|
||||
<Size><AbsDimension x="44" y="44"/></Size>
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
</Texture>
|
||||
|
||||
<!-- Прогресс-бар: BarTexture указывает на Fill, BG ставится в Layer BACKGROUND -->
|
||||
<StatusBar parentKey="ProgressBar">
|
||||
<BarTexture file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-Fill"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture setAllPoints="true"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-BG"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</StatusBar>
|
||||
|
||||
<!-- Карточка цели: bgFile = Card-Background, edgeFile = Card-Border -->
|
||||
<Backdrop bgFile="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Card-Background"
|
||||
edgeFile="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Card-Border"
|
||||
tile="true">
|
||||
<BackgroundInsets><AbsInset left="4" right="4" top="4" bottom="4"/></BackgroundInsets>
|
||||
<TileSize><AbsValue val="64"/></TileSize>
|
||||
<EdgeSize><AbsValue val="16"/></EdgeSize>
|
||||
</Backdrop>
|
||||
```
|
||||
|
||||
## Сноска
|
||||
|
||||
Это плейсхолдеры на уровне «лучше, чем встроенные UI-Quickslot2». Если в
|
||||
проекте есть художник — финальные текстуры стоит делать в этом же
|
||||
визуальном словаре, чтобы Lua/XML менять не пришлось (только перерисовать
|
||||
PNG того же размера и сконвертировать).
|
||||
|
After Width: | Height: | Size: 578 B |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
@@ -0,0 +1,263 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>PlayerGuide — Texture Sheet</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=EB+Garamond:ital,wght@0,400;0,500;1,400&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; min-height: 100%; }
|
||||
body {
|
||||
font-family: 'EB Garamond', Georgia, serif;
|
||||
background:
|
||||
radial-gradient(ellipse at 50% 0%, #1c2c48 0%, transparent 60%),
|
||||
radial-gradient(ellipse at 100% 100%, #0d1422 0%, transparent 70%),
|
||||
linear-gradient(180deg, #0a1018 0%, #050810 100%);
|
||||
color: #d8c8a0;
|
||||
padding: 60px 50px 80px;
|
||||
}
|
||||
h1 {
|
||||
font-family: 'Cinzel', serif; font-weight: 700;
|
||||
font-size: 36px; margin: 0 0 6px;
|
||||
color: #ffd76a;
|
||||
text-shadow: 0 0 14px rgba(197,151,46,0.5), 0 2px 0 #000;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.sub {
|
||||
font-style: italic; color: #a89878;
|
||||
margin: 0 0 36px; font-size: 16px;
|
||||
}
|
||||
.group {
|
||||
margin: 0 0 44px;
|
||||
}
|
||||
.group-title {
|
||||
font-family: 'Cinzel', serif; font-weight: 700;
|
||||
font-size: 13px; letter-spacing: 0.18em; text-transform: uppercase;
|
||||
color: #ffd76a; text-shadow: 0 1px 0 #000;
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
.group-title::before {
|
||||
content: ''; width: 3px; height: 14px;
|
||||
background: #ffd76a; box-shadow: 0 0 6px #ffd76a88;
|
||||
}
|
||||
.group-title::after {
|
||||
content: ''; flex: 1; height: 1px;
|
||||
background: linear-gradient(90deg, rgba(138,106,38,0.5), transparent);
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
.item {
|
||||
background: linear-gradient(180deg, rgba(28,44,72,0.4), rgba(10,16,24,0.4));
|
||||
border: 1px solid rgba(255,215,106,0.18);
|
||||
border-radius: 3px;
|
||||
padding: 12px 12px 10px;
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
}
|
||||
.preview {
|
||||
width: 100%; aspect-ratio: 1 / 1;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background:
|
||||
linear-gradient(45deg, #0d0905 25%, transparent 25%),
|
||||
linear-gradient(-45deg, #0d0905 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, #0d0905 75%),
|
||||
linear-gradient(-45deg, transparent 75%, #0d0905 75%);
|
||||
background-size: 16px 16px;
|
||||
background-position: 0 0, 0 8px, 8px -8px, -8px 0px;
|
||||
background-color: #18120a;
|
||||
border: 1px solid rgba(125,90,42,0.4);
|
||||
margin-bottom: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.preview.wide { aspect-ratio: 2 / 1; }
|
||||
.preview.bar { aspect-ratio: 4 / 1; }
|
||||
.preview img {
|
||||
max-width: 100%; max-height: 100%;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.name {
|
||||
font-family: 'Cinzel', serif; font-weight: 600;
|
||||
font-size: 11px; color: #ffe9a0;
|
||||
text-shadow: 0 1px 0 #000;
|
||||
letter-spacing: 0.04em; text-align: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.size {
|
||||
font-size: 10px; color: #a89878;
|
||||
font-style: italic; margin-top: 3px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
/* Showcase row */
|
||||
.showcase {
|
||||
margin: 24px 0 40px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
.showcase-card {
|
||||
background: linear-gradient(180deg, rgba(60,40,18,0.35), rgba(25,15,8,0.55));
|
||||
border: 1px solid #5a4220;
|
||||
box-shadow: inset 0 1px 0 rgba(255,200,100,0.18), 0 4px 12px rgba(0,0,0,0.5);
|
||||
border-radius: 2px;
|
||||
padding: 18px;
|
||||
display: flex; gap: 14px; align-items: stretch;
|
||||
}
|
||||
.showcase-card h3 {
|
||||
margin: 0 0 4px;
|
||||
font-family: 'Cinzel', serif; font-weight: 700;
|
||||
font-size: 14px; color: #ffe9a0; text-shadow: 0 1px 0 #000;
|
||||
}
|
||||
.showcase-card p {
|
||||
margin: 0; font-style: italic; font-size: 12px; color: #a89878;
|
||||
}
|
||||
.progress-demo {
|
||||
margin-top: 10px;
|
||||
height: 14px; width: 100%; position: relative;
|
||||
background: url('Card-Background.png');
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>PlayerGuide — лист текстур</h1>
|
||||
<p class="sub">33 PNG-файла, готовы к конвертации в BLP. Шахматный фон под превью = прозрачность.</p>
|
||||
|
||||
<!-- Demo: показываем как они стыкуются вместе -->
|
||||
<div class="group">
|
||||
<div class="group-title">Демонстрация (как это смотрится в карточке)</div>
|
||||
<div class="showcase">
|
||||
<div class="showcase-card">
|
||||
<img src="TypeIcon-Dungeon.png" width="44" height="44" alt="" />
|
||||
<div style="flex:1;">
|
||||
<h3>Пройти Кузню Крови</h3>
|
||||
<p>Цитадель Адского Пламени • рек. уровень 61—64</p>
|
||||
<div style="margin-top:8px; display:flex; gap:8px; align-items:center;">
|
||||
<div style="flex:1; height:8px; background: url('ProgressBar-BG.png'); position:relative;">
|
||||
<div style="width:35%; height:100%; background: url('ProgressBar-Fill.png');"></div>
|
||||
</div>
|
||||
<span style="font-family:Cinzel,serif; font-size:11px; color:#caa760;">0 / 1</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="showcase-card" style="background: url('Card-Background.png'); border-color: #5a4220;">
|
||||
<img src="StageBadge-Active.png" width="48" height="48" alt="" />
|
||||
<div style="flex:1;">
|
||||
<h3 style="color:#ffd76a;">Подготовка к Нордсколу</h3>
|
||||
<p>Этап V из VII · 62% завершено</p>
|
||||
<div style="margin-top:10px; height:14px; position:relative; background: url('ProgressBar-BG.png');">
|
||||
<div style="width:62%; height:100%; background: url('ProgressBar-Fill.png');"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Группы текстур -->
|
||||
<div class="group">
|
||||
<div class="group-title">Бейджи этапов</div>
|
||||
<div class="grid" id="stage"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Прогресс-бар</div>
|
||||
<div class="grid" id="progress"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Карточка</div>
|
||||
<div class="grid" id="card"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Декор</div>
|
||||
<div class="grid" id="decor"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Обложки рекомендаций (биомы)</div>
|
||||
<div class="grid" id="biome"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Иконки типов целей</div>
|
||||
<div class="grid" id="types"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Иконки статусов</div>
|
||||
<div class="grid" id="status"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Кнопки</div>
|
||||
<div class="grid" id="buttons"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const groups = {
|
||||
stage: [
|
||||
['StageBadge-Active', '64×64'],
|
||||
['StageBadge-Done', '64×64'],
|
||||
['StageBadge-Locked', '64×64'],
|
||||
],
|
||||
progress: [
|
||||
['ProgressBar-Fill', '128×16', 'bar'],
|
||||
['ProgressBar-BG', '128×16', 'bar'],
|
||||
],
|
||||
card: [
|
||||
['Card-Background', '256×256'],
|
||||
['Card-Border', '256×32', 'bar'],
|
||||
],
|
||||
decor: [
|
||||
['Section-Divider', '256×16', 'bar'],
|
||||
['Corner-Ornament', '64×64'],
|
||||
['Top-Wave-Glow', '512×64', 'bar'],
|
||||
],
|
||||
biome: [
|
||||
['Biome-Fire', '256×128', 'wide'],
|
||||
['Biome-Arcane', '256×128', 'wide'],
|
||||
['Biome-Storm', '256×128', 'wide'],
|
||||
['Biome-Shadow', '256×128', 'wide'],
|
||||
['Biome-Nature', '256×128', 'wide'],
|
||||
['Biome-Holy', '256×128', 'wide'],
|
||||
],
|
||||
types: [
|
||||
['TypeIcon-Level', '64×64'],
|
||||
['TypeIcon-Quest', '64×64'],
|
||||
['TypeIcon-QuestChain', '64×64'],
|
||||
['TypeIcon-Dungeon', '64×64'],
|
||||
['TypeIcon-Raid', '64×64'],
|
||||
['TypeIcon-Boss', '64×64'],
|
||||
['TypeIcon-Achievement', '64×64'],
|
||||
['TypeIcon-Reputation', '64×64'],
|
||||
['TypeIcon-Profession', '64×64'],
|
||||
['TypeIcon-Item', '64×64'],
|
||||
['TypeIcon-Currency', '64×64'],
|
||||
['TypeIcon-Custom', '64×64'],
|
||||
],
|
||||
status: [
|
||||
['TypeIcon-Done', '64×64'],
|
||||
['TypeIcon-Locked', '64×64'],
|
||||
],
|
||||
buttons: [
|
||||
['Button-Up', '128×32', 'bar'],
|
||||
['Button-Down', '128×32', 'bar'],
|
||||
['Button-Highlight', '128×32', 'bar'],
|
||||
],
|
||||
};
|
||||
|
||||
for (const [groupId, items] of Object.entries(groups)) {
|
||||
const el = document.getElementById(groupId);
|
||||
for (const [name, size, shape] of items) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'item';
|
||||
item.innerHTML = `
|
||||
<div class="preview ${shape || ''}">
|
||||
<img src="${name}.png" alt="${name}" />
|
||||
</div>
|
||||
<div class="name">${name}</div>
|
||||
<div class="size">${size}</div>
|
||||
`;
|
||||
el.appendChild(item);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,448 @@
|
||||
# Player Guide Requirements
|
||||
|
||||
## Goal
|
||||
|
||||
Create a `Путеводитель` tab inside the existing Encounter Journal UI.
|
||||
|
||||
The guide must show personal character progress calculated by `mod-individual-progress` and recommend the next relevant content: quests, dungeons, raids, bosses, requirements, activities, and rewards.
|
||||
|
||||
This feature is not a third Encounter Journal instance type. It is a separate panel inside the same interface.
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
```text
|
||||
AzerothCore / mod-individual-progress
|
||||
-> calculates player progress
|
||||
-> builds a guide payload
|
||||
|
||||
AIO / MoonWell bridge
|
||||
-> sends guide data to the client
|
||||
|
||||
MoonWellClient / EncounterJournal
|
||||
-> receives the payload
|
||||
-> stores it in C_PlayerGuide
|
||||
-> renders the Player Guide tab
|
||||
```
|
||||
|
||||
## Client Components
|
||||
|
||||
Add separate client-side files:
|
||||
|
||||
```text
|
||||
EncounterJournal/Utils/C_PlayerGuide.lua
|
||||
EncounterJournal/Custom_EncounterJournal/PlayerGuide.lua
|
||||
EncounterJournal/Custom_EncounterJournal/PlayerGuide.xml
|
||||
```
|
||||
|
||||
Responsibilities:
|
||||
|
||||
```text
|
||||
C_PlayerGuide.lua
|
||||
- stores current guide data
|
||||
- exposes a small UI-facing API
|
||||
- receives server payloads
|
||||
- requests refreshes from the server
|
||||
|
||||
PlayerGuide.lua
|
||||
- renders guide data
|
||||
- refreshes objective rows
|
||||
- handles clicks on objectives/recommendations
|
||||
|
||||
PlayerGuide.xml
|
||||
- defines the guide panel
|
||||
- defines static frames, headers, scroll area, row templates
|
||||
```
|
||||
|
||||
Register these files in `MoonWellClient.toc` before `Custom_EncounterJournal.lua`.
|
||||
|
||||
## Encounter Journal Tab
|
||||
|
||||
Add a new top-level tab near `Подземелья` and `Рейды`:
|
||||
|
||||
```text
|
||||
Путеводитель | Подземелья | Рейды | Комплекты
|
||||
```
|
||||
|
||||
The tab must live in:
|
||||
|
||||
```text
|
||||
EncounterJournal/Custom_EncounterJournal/Custom_EncounterJournal.xml
|
||||
```
|
||||
|
||||
Expected XML shape:
|
||||
|
||||
```xml
|
||||
<Button name="$parentGuideTab"
|
||||
inherits="EncounterTierTabTemplate"
|
||||
text="MW_PLAYER_GUIDE"
|
||||
parentKey="guideTab"
|
||||
id="5">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="$parentSuggestTab" relativePoint="BOTTOMRIGHT" x="35" y="0"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick function="EJ_ContentTab_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
```
|
||||
|
||||
If the guide tab is inserted before existing tabs, anchors for `DungeonTab`, `RaidTab`, and `LootJournalTab` must be adjusted.
|
||||
|
||||
Add a localized string:
|
||||
|
||||
```lua
|
||||
MW_PLAYER_GUIDE = "Путеводитель"
|
||||
```
|
||||
|
||||
## Tab Selection Behavior
|
||||
|
||||
When the guide tab is selected:
|
||||
|
||||
```text
|
||||
- hide instance cards
|
||||
- hide Encounter Journal detail panel
|
||||
- hide suggested content panel
|
||||
- hide loot journal panel
|
||||
- hide or disable the expansion/tier dropdown unless explicitly needed
|
||||
- show PlayerGuideFrame
|
||||
```
|
||||
|
||||
When any other top-level tab is selected:
|
||||
|
||||
```text
|
||||
- hide PlayerGuideFrame
|
||||
```
|
||||
|
||||
Do not route this tab through:
|
||||
|
||||
```lua
|
||||
EJ_GetInstanceByIndex(index, isRaid)
|
||||
```
|
||||
|
||||
That API is only for dungeon/raid instance lists.
|
||||
|
||||
## Client API
|
||||
|
||||
Create:
|
||||
|
||||
```lua
|
||||
C_PlayerGuide = C_PlayerGuide or {}
|
||||
```
|
||||
|
||||
Required methods:
|
||||
|
||||
```lua
|
||||
C_PlayerGuide.SetData(data)
|
||||
C_PlayerGuide.GetStageInfo()
|
||||
C_PlayerGuide.GetNumObjectives()
|
||||
C_PlayerGuide.GetObjectiveInfo(index)
|
||||
C_PlayerGuide.GetNumRecommendations()
|
||||
C_PlayerGuide.GetRecommendationInfo(index)
|
||||
C_PlayerGuide.GetNumRewards()
|
||||
C_PlayerGuide.GetRewardInfo(index)
|
||||
C_PlayerGuide.RequestRefresh()
|
||||
```
|
||||
|
||||
Minimum behavior:
|
||||
|
||||
```text
|
||||
SetData(data)
|
||||
- validate payload is a table
|
||||
- replace current data
|
||||
- fire/trigger a UI refresh if the guide panel is visible
|
||||
|
||||
GetStageInfo()
|
||||
- returns stageID, stageName, stageDescription, progress
|
||||
|
||||
GetObjectiveInfo(index)
|
||||
- returns a normalized objective record or unpacked objective fields
|
||||
|
||||
RequestRefresh()
|
||||
- asks the server to resend the current payload
|
||||
```
|
||||
|
||||
## Server Payload
|
||||
|
||||
The server should send one normalized payload per character.
|
||||
|
||||
Minimum shape:
|
||||
|
||||
```lua
|
||||
{
|
||||
version = 1,
|
||||
characterGuid = 123,
|
||||
|
||||
stageID = 10,
|
||||
stageName = "Подготовка к Нордсколу",
|
||||
stageDescription = "Завершите ключевые шаги перед переходом дальше.",
|
||||
progress = 40,
|
||||
|
||||
objectives = {
|
||||
{
|
||||
id = 1,
|
||||
type = "level",
|
||||
title = "Достигнуть 68 уровня",
|
||||
description = "",
|
||||
completed = true,
|
||||
progress = 68,
|
||||
required = 68,
|
||||
targetID = 68,
|
||||
order = 1,
|
||||
},
|
||||
{
|
||||
id = 2,
|
||||
type = "quest",
|
||||
title = "Завершить цепочку Темного портала",
|
||||
description = "",
|
||||
completed = false,
|
||||
progress = 3,
|
||||
required = 8,
|
||||
targetID = 10119,
|
||||
order = 2,
|
||||
},
|
||||
{
|
||||
id = 3,
|
||||
type = "dungeon",
|
||||
title = "Пройти Кузню Крови",
|
||||
description = "",
|
||||
completed = false,
|
||||
progress = 0,
|
||||
required = 1,
|
||||
targetID = 256,
|
||||
instanceID = 256,
|
||||
order = 3,
|
||||
},
|
||||
},
|
||||
|
||||
recommendations = {
|
||||
{
|
||||
id = 1,
|
||||
type = "dungeon",
|
||||
title = "Кузня Крови",
|
||||
description = "Подходит для текущего этапа.",
|
||||
instanceID = 256,
|
||||
priority = 100,
|
||||
},
|
||||
},
|
||||
|
||||
rewards = {
|
||||
{
|
||||
type = "item",
|
||||
itemID = 12345,
|
||||
count = 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Objective Types
|
||||
|
||||
Support at least:
|
||||
|
||||
```text
|
||||
level
|
||||
quest
|
||||
quest_chain
|
||||
dungeon
|
||||
raid
|
||||
boss
|
||||
achievement
|
||||
reputation
|
||||
profession
|
||||
item
|
||||
currency
|
||||
custom
|
||||
```
|
||||
|
||||
Every objective should use the common fields:
|
||||
|
||||
```text
|
||||
id
|
||||
type
|
||||
title
|
||||
description
|
||||
completed
|
||||
progress
|
||||
required
|
||||
targetID
|
||||
order
|
||||
```
|
||||
|
||||
Optional type-specific fields:
|
||||
|
||||
```text
|
||||
questID
|
||||
questChainID
|
||||
instanceID
|
||||
encounterID
|
||||
achievementID
|
||||
factionID
|
||||
professionID
|
||||
itemID
|
||||
currencyID
|
||||
```
|
||||
|
||||
## Objective Click Behavior
|
||||
|
||||
Recommended behavior:
|
||||
|
||||
```text
|
||||
quest
|
||||
- show quest link or quest hint if available
|
||||
|
||||
dungeon
|
||||
- open Encounter Journal on instanceID
|
||||
|
||||
raid
|
||||
- open Encounter Journal on instanceID
|
||||
|
||||
boss
|
||||
- open Encounter Journal on encounterID
|
||||
|
||||
item
|
||||
- show item tooltip
|
||||
|
||||
achievement
|
||||
- show achievement UI/link
|
||||
|
||||
custom
|
||||
- show text-only detail or request a server action
|
||||
```
|
||||
|
||||
If a target cannot be opened, the UI should fail quietly and keep the guide visible.
|
||||
|
||||
## AIO Contract
|
||||
|
||||
Use a separate handler namespace:
|
||||
|
||||
```text
|
||||
MoonWellPlayerGuide
|
||||
```
|
||||
|
||||
Server to client:
|
||||
|
||||
```lua
|
||||
SetData(payload)
|
||||
UpdateObjective(objective)
|
||||
SetStage(stagePayload)
|
||||
Notify(message)
|
||||
```
|
||||
|
||||
Client to server:
|
||||
|
||||
```lua
|
||||
RequestRefresh()
|
||||
RequestTrackObjective(objectiveID)
|
||||
RequestOpenTarget(objectiveID)
|
||||
```
|
||||
|
||||
Minimum MVP only needs:
|
||||
|
||||
```lua
|
||||
SetData(payload)
|
||||
RequestRefresh()
|
||||
```
|
||||
|
||||
## AzerothCore / mod-individual-progress Requirements
|
||||
|
||||
`mod-individual-progress` must:
|
||||
|
||||
```text
|
||||
- determine the player's current progression stage
|
||||
- calculate objective completion state
|
||||
- calculate objective progress values
|
||||
- build the guide payload
|
||||
- send the payload on login
|
||||
- send updates when progress changes
|
||||
- respond to explicit client refresh requests
|
||||
```
|
||||
|
||||
Recommended update events:
|
||||
|
||||
```text
|
||||
login
|
||||
level up
|
||||
quest accepted
|
||||
quest completed
|
||||
quest rewarded
|
||||
achievement earned
|
||||
boss kill
|
||||
dungeon completed
|
||||
reputation changed
|
||||
profession changed
|
||||
item acquired, if item objectives are used
|
||||
```
|
||||
|
||||
## UI Requirements
|
||||
|
||||
MVP UI:
|
||||
|
||||
```text
|
||||
- guide tab
|
||||
- stage title
|
||||
- stage description
|
||||
- total progress percentage
|
||||
- objective list
|
||||
- completed/incomplete visual state
|
||||
- recommendation list
|
||||
- manual refresh button
|
||||
```
|
||||
|
||||
Later UI:
|
||||
|
||||
```text
|
||||
- categories
|
||||
- filters: all / active / completed
|
||||
- tracked objective pinning
|
||||
- reward preview
|
||||
- requirement tooltips
|
||||
- direct "Open in Journal" buttons
|
||||
- server notifications
|
||||
```
|
||||
|
||||
## Non-Goals
|
||||
|
||||
Do not:
|
||||
|
||||
```text
|
||||
- store guide progress in JOURNALINSTANCE
|
||||
- make the guide a third dungeon/raid instance category
|
||||
- overload EJ_GetInstanceByIndex(index, isRaid)
|
||||
- depend on Sirus-only globals or realm constants
|
||||
- block the old dungeon/raid Encounter Journal if guide data is missing
|
||||
```
|
||||
|
||||
## MVP Implementation Order
|
||||
|
||||
1. Add the `Путеводитель` tab.
|
||||
2. Add an empty `PlayerGuideFrame`.
|
||||
3. Add `C_PlayerGuide.lua` with static test data.
|
||||
4. Render the stage title and three test objectives.
|
||||
5. Wire tab switching and hide/show behavior.
|
||||
6. Add AIO `MoonWellPlayerGuide.SetData`.
|
||||
7. Send test payload from the server.
|
||||
8. Replace static data with real `mod-individual-progress` data.
|
||||
9. Add click behavior for dungeon/raid/boss objectives.
|
||||
10. Add polish: progress bar, icons, rewards, filters.
|
||||
|
||||
## Validation
|
||||
|
||||
Client should tolerate missing or malformed fields:
|
||||
|
||||
```text
|
||||
- missing payload -> show empty state
|
||||
- missing objectives -> show "no active objectives"
|
||||
- missing recommendations -> hide recommendations block
|
||||
- unknown objective type -> render as text-only custom objective
|
||||
- invalid instanceID/encounterID -> do not throw Lua errors
|
||||
```
|
||||
|
||||
Server should validate:
|
||||
|
||||
```text
|
||||
- objective ids are unique within the payload
|
||||
- order is stable
|
||||
- type is known or custom
|
||||
- progress <= required when required is present
|
||||
- dungeon/raid/boss targets exist in Encounter Journal data when used
|
||||
```
|
||||
@@ -1,3 +1,9 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[ValidateSet("production", "ptr", "local")]
|
||||
[string]$Env = "local"
|
||||
)
|
||||
|
||||
# Stop on errors
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
@@ -70,6 +76,21 @@ if ($LASTEXITCODE -ge 8) {
|
||||
|
||||
Write-Host "MPQ archives deployed to $WOW_HOME"
|
||||
|
||||
# --- Write realmlist.wtf based on selected environment
|
||||
$realmlist = switch ($Env) {
|
||||
"production" { $env:PRODUCTION_REALMLIST }
|
||||
"ptr" { $env:PTR_REALMLIST }
|
||||
"local" { $env:LOCAL_REALMLIST }
|
||||
}
|
||||
|
||||
if ($realmlist) {
|
||||
$realmlistPath = Join-Path $WOW_HOME "Data\ruRU\realmlist.wtf"
|
||||
Set-Content -Path $realmlistPath -Value "set realmlist $realmlist" -Encoding ASCII
|
||||
Write-Host "Realmlist ($Env): $realmlist"
|
||||
} else {
|
||||
Write-Warning "REALMLIST for '$Env' is not set in .env -- skipping realmlist.wtf"
|
||||
}
|
||||
|
||||
# --- Run WoW reload script
|
||||
Write-Host "Launching WoW..."
|
||||
cmd /c $RELOAD_SCRIPT
|
||||
|
||||
@@ -69,25 +69,26 @@ local MainMenuBarBackpackButton = _G.MainMenuBarBackpackButton;
|
||||
local HelpMicroButton = _G.HelpMicroButton;
|
||||
local KeyRingButton = _G.KeyRingButton;
|
||||
|
||||
-- Button collections (dynamically set based on server)
|
||||
local MICRO_BUTTONS
|
||||
-- Button collections (rebuilt after addons create optional custom buttons).
|
||||
local function BuildMicroButtonList()
|
||||
if isAscensionServer then
|
||||
return {
|
||||
_G.CharacterMicroButton,
|
||||
_G.SpellbookMicroButton,
|
||||
_G.TalentMicroButton,
|
||||
_G.AchievementMicroButton,
|
||||
_G.QuestLogMicroButton,
|
||||
_G.SocialsMicroButton,
|
||||
_G.LFDMicroButton,
|
||||
_G.EncounterJournalMicroButton,
|
||||
_G.PathToAscensionMicroButton,
|
||||
_G.ChallengesMicroButton,
|
||||
_G.MainMenuMicroButton,
|
||||
_G.HelpMicroButton
|
||||
}
|
||||
end
|
||||
|
||||
if isAscensionServer then
|
||||
MICRO_BUTTONS = {
|
||||
_G.CharacterMicroButton,
|
||||
_G.SpellbookMicroButton,
|
||||
_G.TalentMicroButton,
|
||||
_G.AchievementMicroButton,
|
||||
_G.QuestLogMicroButton,
|
||||
_G.SocialsMicroButton,
|
||||
_G.LFDMicroButton,
|
||||
_G.PathToAscensionMicroButton,
|
||||
_G.ChallengesMicroButton,
|
||||
_G.MainMenuMicroButton,
|
||||
_G.HelpMicroButton
|
||||
}
|
||||
else
|
||||
MICRO_BUTTONS = {
|
||||
return {
|
||||
_G.CharacterMicroButton,
|
||||
_G.SpellbookMicroButton,
|
||||
_G.TalentMicroButton,
|
||||
@@ -95,6 +96,7 @@ else
|
||||
_G.QuestLogMicroButton,
|
||||
_G.SocialsMicroButton,
|
||||
_G.LFDMicroButton,
|
||||
_G.EncounterJournalMicroButton,
|
||||
_G.CollectionsMicroButton,
|
||||
_G.PVPMicroButton,
|
||||
_G.MainMenuMicroButton,
|
||||
@@ -102,6 +104,7 @@ else
|
||||
}
|
||||
end
|
||||
|
||||
local MICRO_BUTTONS = BuildMicroButtonList()
|
||||
|
||||
local bagslots = {_G.CharacterBag0Slot, _G.CharacterBag1Slot, _G.CharacterBag2Slot, _G.CharacterBag3Slot};
|
||||
|
||||
@@ -783,6 +786,8 @@ local function ApplyMicromenuSystem()
|
||||
return
|
||||
end
|
||||
|
||||
MICRO_BUTTONS = BuildMicroButtonList()
|
||||
|
||||
-- Store original states first
|
||||
StoreOriginalMicroButtonStates()
|
||||
|
||||
@@ -911,6 +916,8 @@ local function ApplyMicromenuSystem()
|
||||
or (_G.PVPParentFrame and _G.PVPParentFrame:IsVisible() and true or false)
|
||||
or (_G.BattlefieldFrame and _G.BattlefieldFrame:IsVisible() and true or false)
|
||||
or (_G.HonorFrame and _G.HonorFrame:IsVisible() and true or false)
|
||||
elseif buttonName == "EncounterJournal" then
|
||||
return (_G.EncounterJournal and _G.EncounterJournal:IsVisible() and true or false)
|
||||
end
|
||||
|
||||
return pressed
|
||||
@@ -919,6 +926,152 @@ local function ApplyMicromenuSystem()
|
||||
-- ============================================================================
|
||||
-- SECTION 5: SPECIALIZED BUTTON SETUP
|
||||
-- ============================================================================
|
||||
local function SetupEncounterJournalButton(button)
|
||||
local iconTexture = 'Interface\\EncounterJournal\\UI-EJ-PortraitIcon'
|
||||
local backgroundTexture = 'Interface\\AddOns\\DragonUI\\Textures\\Micromenu\\uimicromenu2x'
|
||||
local buttonWidth, buttonHeight = button:GetSize()
|
||||
local dx, dy = -1, 1
|
||||
local offX, offY = button:GetPushedTextOffset()
|
||||
|
||||
if not button.DragonUIEncounterJournalIcon then
|
||||
button.DragonUIEncounterJournalIcon = button:CreateTexture(nil, 'ARTWORK')
|
||||
end
|
||||
|
||||
local icon = button.DragonUIEncounterJournalIcon
|
||||
icon:SetTexture(iconTexture)
|
||||
icon:SetTexCoord(0, 1, 0, 1)
|
||||
icon:ClearAllPoints()
|
||||
icon:SetPoint('CENTER', button, 'CENTER', 0, 0)
|
||||
icon:SetSize(buttonWidth - 8, buttonWidth - 8)
|
||||
icon:SetAlpha(1)
|
||||
icon:Show()
|
||||
|
||||
local highlightTexture = button:GetHighlightTexture()
|
||||
if highlightTexture then
|
||||
highlightTexture:SetTexture(iconTexture)
|
||||
highlightTexture:SetTexCoord(0, 1, 0, 1)
|
||||
highlightTexture:ClearAllPoints()
|
||||
highlightTexture:SetAllPoints(icon)
|
||||
highlightTexture:SetBlendMode('ADD')
|
||||
highlightTexture:SetAlpha(0.55)
|
||||
end
|
||||
|
||||
if not button.DragonUIBackground then
|
||||
local bg = button:CreateTexture(nil, 'BACKGROUND')
|
||||
bg:SetTexture(backgroundTexture)
|
||||
bg:SetSize(buttonWidth, buttonHeight + 1)
|
||||
bg:SetTexCoord(0.0654297, 0.12793, 0.330078, 0.490234)
|
||||
bg:SetPoint('CENTER', dx, dy)
|
||||
button.DragonUIBackground = bg
|
||||
|
||||
local bgPushed = button:CreateTexture(nil, 'BACKGROUND')
|
||||
bgPushed:SetTexture(backgroundTexture)
|
||||
bgPushed:SetSize(buttonWidth, buttonHeight + 1)
|
||||
bgPushed:SetTexCoord(0.0654297, 0.12793, 0.494141, 0.654297)
|
||||
bgPushed:SetPoint('CENTER', dx + offX, dy + offY)
|
||||
bgPushed:Hide()
|
||||
button.DragonUIBackgroundPushed = bgPushed
|
||||
else
|
||||
button.DragonUIBackground:SetTexture(backgroundTexture)
|
||||
button.DragonUIBackground:SetTexCoord(0.0654297, 0.12793, 0.330078, 0.490234)
|
||||
button.DragonUIBackground:ClearAllPoints()
|
||||
button.DragonUIBackground:SetPoint('CENTER', dx, dy)
|
||||
button.DragonUIBackground:SetSize(buttonWidth, buttonHeight + 1)
|
||||
|
||||
if button.DragonUIBackgroundPushed then
|
||||
button.DragonUIBackgroundPushed:SetTexture(backgroundTexture)
|
||||
button.DragonUIBackgroundPushed:SetTexCoord(0.0654297, 0.12793, 0.494141, 0.654297)
|
||||
button.DragonUIBackgroundPushed:ClearAllPoints()
|
||||
button.DragonUIBackgroundPushed:SetPoint('CENTER', dx + offX, dy + offY)
|
||||
button.DragonUIBackgroundPushed:SetSize(buttonWidth, buttonHeight + 1)
|
||||
end
|
||||
end
|
||||
|
||||
button.dragonUIState = button.dragonUIState or {}
|
||||
button.dragonUIState.pushed = IsSpecialMicroButtonActive(button, "EncounterJournal")
|
||||
button.dragonUILastState = button.dragonUIState.pushed
|
||||
button.dragonUITimer = button.dragonUITimer or 0
|
||||
|
||||
button.HandleDragonUIState = function()
|
||||
local state = button.dragonUIState
|
||||
local hlTex = button:GetHighlightTexture()
|
||||
if state and state.pushed then
|
||||
if icon then
|
||||
icon:ClearAllPoints()
|
||||
icon:SetPoint('CENTER', button, 'CENTER', offX, offY)
|
||||
icon:SetAlpha(0.7)
|
||||
end
|
||||
if button.DragonUIBackground then
|
||||
button.DragonUIBackground:Hide()
|
||||
end
|
||||
if button.DragonUIBackgroundPushed then
|
||||
button.DragonUIBackgroundPushed:Show()
|
||||
end
|
||||
if hlTex then
|
||||
hlTex:ClearAllPoints()
|
||||
hlTex:SetPoint('TOPLEFT', icon, 'TOPLEFT', 0, 0)
|
||||
hlTex:SetPoint('BOTTOMRIGHT', icon, 'BOTTOMRIGHT', 0, 0)
|
||||
end
|
||||
else
|
||||
if icon then
|
||||
icon:ClearAllPoints()
|
||||
icon:SetPoint('CENTER', button, 'CENTER', 0, 0)
|
||||
icon:SetAlpha(1)
|
||||
end
|
||||
if button.DragonUIBackground then
|
||||
button.DragonUIBackground:Show()
|
||||
end
|
||||
if button.DragonUIBackgroundPushed then
|
||||
button.DragonUIBackgroundPushed:Hide()
|
||||
end
|
||||
if hlTex then
|
||||
hlTex:ClearAllPoints()
|
||||
hlTex:SetAllPoints(icon)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
button:SetScript('OnUpdate', function(self, elapsed)
|
||||
self.dragonUITimer = (self.dragonUITimer or 0) + elapsed
|
||||
if self.dragonUITimer >= 0.1 then
|
||||
self.dragonUITimer = 0
|
||||
local currentState = IsSpecialMicroButtonActive(self, "EncounterJournal")
|
||||
if currentState ~= self.dragonUILastState then
|
||||
self.dragonUILastState = currentState
|
||||
if self.dragonUIState then
|
||||
self.dragonUIState.pushed = currentState
|
||||
end
|
||||
if self.HandleDragonUIState then
|
||||
self.HandleDragonUIState()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
if not button.DragonUIStateHooks then
|
||||
button:HookScript('OnMouseDown', function(self)
|
||||
if self.dragonUIState then
|
||||
self.dragonUIState.pushed = true
|
||||
end
|
||||
if self.HandleDragonUIState then
|
||||
self.HandleDragonUIState()
|
||||
end
|
||||
end)
|
||||
button:HookScript('OnMouseUp', function(self)
|
||||
local currentState = IsSpecialMicroButtonActive(self, "EncounterJournal")
|
||||
if self.dragonUIState then
|
||||
self.dragonUIState.pushed = currentState
|
||||
end
|
||||
if self.HandleDragonUIState then
|
||||
self.HandleDragonUIState()
|
||||
end
|
||||
end)
|
||||
button.DragonUIStateHooks = true
|
||||
end
|
||||
|
||||
button.HandleDragonUIState()
|
||||
end
|
||||
|
||||
local function SetupPVPButton(button)
|
||||
-- Mirror the Character button pattern:
|
||||
-- Instead of fighting WoW's internal NormalTexture alpha management,
|
||||
@@ -1582,6 +1735,8 @@ local function ApplyMicromenuSystem()
|
||||
end
|
||||
|
||||
local function setupMicroButtons(xOffset)
|
||||
MICRO_BUTTONS = BuildMicroButtonList()
|
||||
|
||||
local buttonxOffset = 0
|
||||
|
||||
local useGrayscale = addon.db.profile.micromenu.grayscale_icons
|
||||
@@ -1705,9 +1860,11 @@ local function ApplyMicromenuSystem()
|
||||
|
||||
local isCharacterButton = (buttonName == "Character")
|
||||
local isPVPButton = (buttonName == "PVP")
|
||||
local isEncounterJournalButton = (buttonName == "EncounterJournal")
|
||||
|
||||
local upCoords = not isCharacterButton and not isPVPButton and GetColoredTextureCoords(name, "Up") or nil
|
||||
local shouldUseGrayscale = useGrayscale or (not isPVPButton and not upCoords and not isCharacterButton)
|
||||
local upCoords = not isCharacterButton and not isPVPButton and not isEncounterJournalButton and GetColoredTextureCoords(name, "Up") or nil
|
||||
local shouldUseGrayscale = (useGrayscale and not isEncounterJournalButton) or
|
||||
(not isPVPButton and not isEncounterJournalButton and not upCoords and not isCharacterButton)
|
||||
|
||||
if shouldUseGrayscale then
|
||||
-- Grayscale icons
|
||||
@@ -1741,6 +1898,8 @@ local function ApplyMicromenuSystem()
|
||||
end
|
||||
elseif isPVPButton then
|
||||
SetupPVPButton(button)
|
||||
elseif isEncounterJournalButton then
|
||||
SetupEncounterJournalButton(button)
|
||||
elseif isCharacterButton then
|
||||
SetupCharacterButton(button)
|
||||
else
|
||||
@@ -2245,6 +2404,8 @@ end
|
||||
return
|
||||
end
|
||||
|
||||
MICRO_BUTTONS = BuildMicroButtonList()
|
||||
|
||||
local useGrayscale = addon.db.profile.micromenu.grayscale_icons
|
||||
local configMode = useGrayscale and "grayscale" or "normal"
|
||||
local config = addon.db.profile.micromenu[configMode]
|
||||
|
||||
@@ -0,0 +1,544 @@
|
||||
UIPanelWindows["ItemBrowser"] = { area = "left", pushable = 0, whileDead = 1, width = 830, xOffset = "15", yOffset = "-10"}
|
||||
|
||||
local type = type
|
||||
local mathmax = math.max
|
||||
local strfind, strlower, strtrim = string.find, string.lower, string.trim
|
||||
local tinsert, twipe = table.insert, table.wipe
|
||||
local debugprofilestop = debugprofilestop
|
||||
|
||||
local COLUMNS = {
|
||||
{
|
||||
name = "Name",
|
||||
dataProviderKey = "name",
|
||||
template = "ItemBrowserListCellNameIconTemplate",
|
||||
},
|
||||
{
|
||||
name = "ItemLevel",
|
||||
dataProviderKey = "itemLevel",
|
||||
width = 70,
|
||||
template = "ItemBrowserListCellItemLevelTemplate",
|
||||
},
|
||||
{
|
||||
name = "ID",
|
||||
dataProviderKey = "itemID",
|
||||
width = 90,
|
||||
template = "ItemBrowserListCellItemIDTemplate",
|
||||
},
|
||||
}
|
||||
|
||||
ItemBrowserMixin = {}
|
||||
|
||||
function ItemBrowserMixin:OnLoad()
|
||||
self.TitleText:SetText(ITEM_BROWSER)
|
||||
SetPortraitToTexture(self.portrait, [[Interface\Icons\Achievement_General_ClassicBattles_64]])
|
||||
|
||||
self.inset.Bgs:SetAtlas("UI-EJ-BattleforAzeroth")
|
||||
|
||||
do -- tabs
|
||||
self:RegisterCustomEvent("SERVICE_DATA_UPDATE")
|
||||
self:RegisterCustomEvent("CUSTOM_CHALLENGE_DEACTIVATED")
|
||||
|
||||
self.tab1:SetFrameLevel(1)
|
||||
self.tab2:SetFrameLevel(1)
|
||||
self.tab3:SetFrameLevel(1)
|
||||
self.tab4:SetFrameLevel(1)
|
||||
|
||||
self.maxTabWidth = (self:GetWidth() - 19) / 4
|
||||
|
||||
PanelTemplates_SetNumTabs(self, 4)
|
||||
end
|
||||
|
||||
do -- coroutine
|
||||
self.COROUTINE_HANDLER = CreateFrame("Frame")
|
||||
self.COROUTINE_HANDLER:Hide()
|
||||
|
||||
self.COROUTINE_HANDLER.FRAMETIME_TARGET = 1 / 55
|
||||
self.COROUTINE_HANDLER.FRAMETIME_AVAILABLE = 8
|
||||
self.COROUTINE_HANDLER.FRAMETIME_RESERVE = 4
|
||||
self.COROUTINE_HANDLER.DEBUG = false
|
||||
|
||||
self.COROUTINE_HANDLER:SetScript("OnUpdate", function(this, elapsed)
|
||||
local frametimeStep = this.FRAMETIME_TARGET - elapsed
|
||||
|
||||
if frametimeStep ~= 0 then
|
||||
frametimeStep = frametimeStep * 1000
|
||||
this.FRAMETIME_AVAILABLE = mathmax(5, this.FRAMETIME_AVAILABLE + frametimeStep)
|
||||
end
|
||||
|
||||
if this.COROUTINE then
|
||||
this.COROUTINE_TIMESTAMP = debugprofilestop()
|
||||
local status, result, progress = coroutine.resume(this.COROUTINE)
|
||||
if not status then
|
||||
this.COROUTINE = nil
|
||||
this:Hide()
|
||||
self.PROGRESS = 1
|
||||
error(result, 2)
|
||||
elseif not result then
|
||||
if this.DEBUG then print("ITEM_BROWSER_COROUTINE", progress) end
|
||||
self.PROGRESS = progress
|
||||
self:OnSearchResultUpdate()
|
||||
else
|
||||
if this.DEBUG then print("ITEM_BROWSER_COROUTINE", 1) end
|
||||
this.COROUTINE = nil
|
||||
this:Hide()
|
||||
self.PROGRESS = 1
|
||||
self:OnSearchResultUpdate()
|
||||
end
|
||||
else
|
||||
this:Hide()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
self.BUTTON_OFFSET_Y = 0
|
||||
self.PROGRESS = 1
|
||||
|
||||
self.results = {}
|
||||
|
||||
self.Scroll.ScrollBar:SetBackgroundShown(false)
|
||||
self.Scroll.update = function(scrollFrame)
|
||||
self:UpdateResultList()
|
||||
end
|
||||
self.Scroll.ScrollBar.doNotHide = true
|
||||
self.Scroll.scrollBar = self.Scroll.ScrollBar
|
||||
HybridScrollFrame_CreateButtons(self.Scroll, "ItemBrowserListRowButtonTemplate", 0, 0, nil, nil, nil, -self.BUTTON_OFFSET_Y)
|
||||
|
||||
self.SearchBox:ToggleOnCharFilter(true, 0.3)
|
||||
|
||||
self.tableBuilder = CreateTableBuilder(HybridScrollFrame_GetButtons(self.Scroll))
|
||||
self.tableBuilder:SetHeaderContainer(self.HeaderHolder)
|
||||
self:ConstructTable()
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:OnShow()
|
||||
SetParentFrameLevel(self.inset)
|
||||
PanelTemplates_SetTab(self, 4)
|
||||
|
||||
self.dirty = true
|
||||
self.Scroll.ScrollBar:SetValue(0)
|
||||
self:UpdateResultList()
|
||||
|
||||
self.SearchBox:SetFocus()
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:OnEvent(event, ...)
|
||||
if event == "SERVICE_DATA_UPDATE"
|
||||
or (event == "CUSTOM_CHALLENGE_DEACTIVATED" and select(2, ...) ~= Enum.HardcoreDeathReason.FAILED_DEATH)
|
||||
then
|
||||
if not C_Service.IsRenegadeRealm() then
|
||||
PanelTemplates_HideTab(self, 2)
|
||||
self.tab3:SetPoint("LEFT", self.tab1, "RIGHT", -16, 0);
|
||||
end
|
||||
if not C_Service.IsHardcoreEnabledOnRealm() then
|
||||
PanelTemplates_HideTab(self, 3)
|
||||
end
|
||||
if not C_Service.IsGMAccount() and not IsInterfaceDevClient() then
|
||||
PanelTemplates_HideTab(self, 4)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:GetRowProvider()
|
||||
return function(itemIndex)
|
||||
if itemIndex > #self.results then
|
||||
return nil
|
||||
end
|
||||
|
||||
local itemID = self.results[itemIndex]
|
||||
local itemName, itemLink, itemRarity, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, itemTexture, vendorPrice = C_Item.GetItemInfo(itemID, false, nil, true, true)
|
||||
|
||||
return {
|
||||
itemID = itemID,
|
||||
name = itemName,
|
||||
itemLink = itemLink,
|
||||
itemLevel = itemLevel,
|
||||
itemType = itemType,
|
||||
itemSubType = itemSubType,
|
||||
icon = itemTexture,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:ConstructTable()
|
||||
local textPadding = 1
|
||||
local cellPadding = 10
|
||||
|
||||
self.tableBuilder:Reset()
|
||||
self.tableBuilder:SetDataProvider(self:GetRowProvider())
|
||||
self.tableBuilder:SetTableMargins(5)
|
||||
self.tableBuilder:SetTableWidth(self.Scroll:GetWidth() or select(3, self.Scroll:GetRect()))
|
||||
|
||||
for index, columnInfo in ipairs(COLUMNS) do
|
||||
local column = self.tableBuilder:AddColumn()
|
||||
column:ConstructHeader("Button", "ItemBrowserListHeaderButtonTemplate", columnInfo.name)
|
||||
column:ConstrainToHeader(textPadding)
|
||||
column:ConstructCells("Frame", columnInfo.template)
|
||||
|
||||
if columnInfo.width then
|
||||
column:SetFixedConstraints(columnInfo.width, cellPadding)
|
||||
else
|
||||
column:SetFillConstraints(1, cellPadding)
|
||||
end
|
||||
end
|
||||
|
||||
self.tableBuilder:Arrange()
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:GetNumItems()
|
||||
if not self.NUM_ITEMS then
|
||||
self.NUM_ITEMS = tCount(ItemsCache)
|
||||
end
|
||||
return self.NUM_ITEMS
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:SearchItem(item)
|
||||
item = strtrim(item)
|
||||
if item == "" then
|
||||
self:ClearResults()
|
||||
return
|
||||
end
|
||||
|
||||
local itemID = C_Item.GetItemIDFromString(item)
|
||||
local searchField, searchValue, useStringFind
|
||||
|
||||
if itemID then
|
||||
searchField = Enum.ItemCacheField.ITEM_ID
|
||||
searchValue = itemID
|
||||
elseif item ~= "" then
|
||||
searchField = GetLocale() == "ruRU" and Enum.ItemCacheField.NAME_RURU or Enum.ItemCacheField.NAME_ENGB
|
||||
searchValue = strlower(item)
|
||||
useStringFind = true
|
||||
end
|
||||
|
||||
if not searchField then
|
||||
self:ClearResults()
|
||||
return
|
||||
end
|
||||
|
||||
self:StartSearch(searchField, searchValue, useStringFind)
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:StartSearch(searchField, searchValue, useStringFind)
|
||||
if self.SEARCH_FIELD == searchField and self.SEARCH_VALUE == searchValue then
|
||||
return
|
||||
end
|
||||
|
||||
if self.COROUTINE then
|
||||
coroutine.close(self.COROUTINE)
|
||||
self.COROUTINE = nil
|
||||
self:ClearResults()
|
||||
end
|
||||
|
||||
self.SEARCH_FIELD = searchField
|
||||
self.SEARCH_VALUE = searchValue
|
||||
self.USE_STRING_FIND = useStringFind
|
||||
|
||||
local framerate = GetFramerate()
|
||||
self.COROUTINE_HANDLER.FRAMETIME_TARGET = framerate > 63 and (1 / 60) or (1 / 55)
|
||||
self.COROUTINE_HANDLER.FRAMETIME_BUDGET = 1000 / framerate - self.COROUTINE_HANDLER.FRAMETIME_RESERVE
|
||||
|
||||
self.COROUTINE_HANDLER.COROUTINE = coroutine.create(self.SearchProcess)
|
||||
self.COROUTINE_HANDLER.COROUTINE_TIMESTAMP = debugprofilestop()
|
||||
|
||||
local status, result, progress = coroutine.resume(self.COROUTINE_HANDLER.COROUTINE, self)
|
||||
if not status then
|
||||
self.COROUTINE_HANDLER.COROUTINE = nil
|
||||
self.COROUTINE_HANDLER:Hide()
|
||||
self.PROGRESS = 1
|
||||
self:ClearResults()
|
||||
geterrorhandler()(result)
|
||||
elseif coroutine.status(self.COROUTINE_HANDLER.COROUTINE) == "dead" then
|
||||
self.COROUTINE_HANDLER.COROUTINE = nil
|
||||
self.PROGRESS = 1
|
||||
else
|
||||
if self.COROUTINE_HANDLER.DEBUG then print("ITEM_BROWSER_COROUTINE", progress) end
|
||||
self.PROGRESS = progress
|
||||
self.COROUTINE_HANDLER:Show()
|
||||
end
|
||||
|
||||
self:OnSearchResultUpdate()
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:SearchProcess()
|
||||
twipe(self.results)
|
||||
|
||||
if not ItemsCache then
|
||||
return true, 1
|
||||
end
|
||||
|
||||
local processedEntries = 0
|
||||
local fieldValue
|
||||
|
||||
for itemID, itemInfo in pairs(ItemsCache) do
|
||||
processedEntries = processedEntries + 1
|
||||
|
||||
fieldValue = itemInfo[self.SEARCH_FIELD]
|
||||
|
||||
if type(fieldValue) == "string" then
|
||||
fieldValue = strlower(fieldValue)
|
||||
end
|
||||
|
||||
if fieldValue == self.SEARCH_VALUE
|
||||
or (self.USE_STRING_FIND and strfind(fieldValue, self.SEARCH_VALUE, 1, true))
|
||||
then
|
||||
tinsert(self.results, itemInfo[Enum.ItemCacheField.ITEM_ID])
|
||||
end
|
||||
|
||||
if (debugprofilestop() - self.COROUTINE_HANDLER.COROUTINE_TIMESTAMP) > self.COROUTINE_HANDLER.FRAMETIME_BUDGET then
|
||||
coroutine.yield(false, processedEntries / self:GetNumItems())
|
||||
end
|
||||
end
|
||||
|
||||
return true, 1
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:OnSearchResultUpdate()
|
||||
self.dirty = true
|
||||
self:UpdateSearchProgress()
|
||||
self:UpdateResultList()
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:UpdateSearchProgress()
|
||||
local progress = Clamp(self.PROGRESS or 1, 0, 1)
|
||||
self.SearchProgressBar:SetValue(progress)
|
||||
self.SearchProgressBar:SetAlpha(progress < 1 and 1 or 0)
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:ClearResults()
|
||||
twipe(self.results)
|
||||
|
||||
self.SEARCH_FIELD = nil
|
||||
self.SEARCH_VALUE = nil
|
||||
self.USE_STRING_FIND = nil
|
||||
|
||||
self.PROGRESS = 1
|
||||
self.dirty = true
|
||||
|
||||
self:UpdateSearchProgress()
|
||||
self:UpdateResultList()
|
||||
end
|
||||
|
||||
function ItemBrowserMixin:UpdateResultList()
|
||||
local scrollFrame = self.Scroll
|
||||
local offset = HybridScrollFrame_GetOffset(scrollFrame)
|
||||
local numResults = self.results and #self.results or 0
|
||||
|
||||
if self.numItems ~= numResults then
|
||||
self.numItems = numResults
|
||||
self.dirty = true
|
||||
end
|
||||
|
||||
local populateCount = math.min(#scrollFrame.buttons, numResults)
|
||||
self.tableBuilder:Populate(offset, populateCount)
|
||||
|
||||
local mouseFocus = GetMouseFocus()
|
||||
for index, button in ipairs(scrollFrame.buttons) do
|
||||
button:SetOwner(self)
|
||||
button:SetShown(index <= numResults)
|
||||
if button == mouseFocus then
|
||||
button:OnEnter()
|
||||
end
|
||||
end
|
||||
|
||||
if self.dirty then
|
||||
local buttonHeight = scrollFrame.buttons[1] and scrollFrame.buttons[1]:GetHeight() or 0
|
||||
local scrollHeight = buttonHeight * numResults + self.BUTTON_OFFSET_Y * (numResults - 1) + self.BUTTON_OFFSET_Y / 3
|
||||
HybridScrollFrame_Update(scrollFrame, scrollHeight, scrollFrame:GetHeight())
|
||||
self.dirty = nil
|
||||
end
|
||||
end
|
||||
|
||||
ItemBrowserSearchBoxMixin = CreateFromMixins(PKBT_EditBoxMixin)
|
||||
|
||||
function ItemBrowserSearchBoxMixin:OnLoad()
|
||||
PKBT_EditBoxMixin.OnLoad(self)
|
||||
|
||||
local l, r, t, b = self:GetTextInsets()
|
||||
self:SetTextInsets(l + 10, r + 10, t - 1, b)
|
||||
|
||||
self:SetInstructions(SEARCH)
|
||||
self.Instructions:ClearAllPoints()
|
||||
self.Instructions:SetPoint("LEFT", 19, -2)
|
||||
|
||||
self.searchIcon:SetVertexColor(0.6, 0.6, 0.6)
|
||||
|
||||
self:SetCustomCharFilter(function(text)
|
||||
text = string.gsub(text, "%s%s+", " ")
|
||||
text = utf8.gsub(text, "[^A-zА-я0-9':%- ]+", "")
|
||||
return text
|
||||
end)
|
||||
|
||||
self.value = ""
|
||||
end
|
||||
|
||||
function ItemBrowserSearchBoxMixin:OnHide()
|
||||
self:StopOnCharSearchDelay()
|
||||
end
|
||||
|
||||
function ItemBrowserSearchBoxMixin:OnEnterPressed()
|
||||
self:ClearFocus()
|
||||
self:DoSearch()
|
||||
end
|
||||
|
||||
function ItemBrowserSearchBoxMixin:OnEditFocusLost()
|
||||
SearchBoxTemplate_OnEditFocusLost(self)
|
||||
self:HighlightText(0, 0)
|
||||
self:DoSearch()
|
||||
end
|
||||
|
||||
function ItemBrowserSearchBoxMixin:OnUpdate(elapsed)
|
||||
self.searchDelay = self.searchDelay - elapsed
|
||||
|
||||
if self.searchDelay <= 0 then
|
||||
self:SetScript("OnUpdate", nil)
|
||||
self.searchDelay = nil
|
||||
self:DoSearch()
|
||||
end
|
||||
end
|
||||
|
||||
function ItemBrowserSearchBoxMixin:DoSearch()
|
||||
local newValue = self:GetText()
|
||||
if self.value ~= newValue and not (self.value == nil and newValue == "") then
|
||||
self.value = newValue
|
||||
self:GetParent():SearchItem(newValue)
|
||||
end
|
||||
end
|
||||
|
||||
function ItemBrowserSearchBoxMixin:ToggleOnCharFilter(state, searchUpdateDelay)
|
||||
if state then
|
||||
self.searchUpdateDelay = searchUpdateDelay
|
||||
self.searchDelay = searchUpdateDelay
|
||||
else
|
||||
self:SetScript("OnUpdate", nil)
|
||||
self.searchUpdateDelay = nil
|
||||
self.searchDelay = nil
|
||||
end
|
||||
end
|
||||
|
||||
function ItemBrowserSearchBoxMixin:OnTextChanged(userInput)
|
||||
PKBT_EditBoxMixin.OnTextChanged(self, userInput)
|
||||
SearchBoxTemplate_OnTextChanged(self)
|
||||
end
|
||||
|
||||
function ItemBrowserSearchBoxMixin:OnTextValueChanged(value, userInput)
|
||||
if userInput then
|
||||
if self.searchUpdateDelay then
|
||||
self.searchDelay = self.searchUpdateDelay
|
||||
self:SetScript("OnUpdate", self.OnUpdate)
|
||||
end
|
||||
else
|
||||
self:StopOnCharSearchDelay()
|
||||
end
|
||||
end
|
||||
|
||||
function ItemBrowserSearchBoxMixin:OnUpdate(elapsed)
|
||||
self.searchDelay = self.searchDelay - elapsed
|
||||
|
||||
if self.searchDelay <= 0 then
|
||||
self:SetScript("OnUpdate", nil)
|
||||
self.searchDelay = nil
|
||||
self:DoSearch()
|
||||
end
|
||||
end
|
||||
|
||||
function ItemBrowserSearchBoxMixin:StopOnCharSearchDelay()
|
||||
self:SetScript("OnUpdate", nil)
|
||||
|
||||
if self.searchUpdateDelay then
|
||||
self.searchDelay = self.searchUpdateDelay
|
||||
end
|
||||
end
|
||||
|
||||
function ItemBrowserSearchBoxMixin:OnClearClick(button)
|
||||
SearchBoxTemplateClearButton_OnClick(self.clearButton, button)
|
||||
self:StopOnCharSearchDelay()
|
||||
self:DoSearch()
|
||||
end
|
||||
|
||||
ItemBrowserListHeaderButtonMixin = CreateFromMixins(TableBuilderElementMixin)
|
||||
|
||||
function ItemBrowserListHeaderButtonMixin:Init(name)
|
||||
self:SetText(name)
|
||||
end
|
||||
|
||||
function ItemBrowserListHeaderButtonMixin:OnClick(button)
|
||||
end
|
||||
|
||||
ItemBrowserListRowButton = CreateFromMixins(PKBT_OwnerMixin, TableBuilderRowMixin)
|
||||
|
||||
function ItemBrowserListRowButton:OnLoad()
|
||||
self.UpdateTooltip = self.OnEnter
|
||||
end
|
||||
|
||||
function ItemBrowserListRowButton:OnEnter()
|
||||
if self.itemLink then
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetHyperlink(self.itemLink)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
end
|
||||
|
||||
function ItemBrowserListRowButton:OnLeave()
|
||||
GameTooltip:Hide()
|
||||
end
|
||||
|
||||
function ItemBrowserListRowButton:OnClick(button)
|
||||
if self.itemLink then
|
||||
HandleModifiedItemClick(self.itemLink)
|
||||
end
|
||||
end
|
||||
|
||||
function ItemBrowserListRowButton:Populate(rowData, dataIndex)
|
||||
self:SetID(dataIndex)
|
||||
self.itemID = rowData.itemID
|
||||
self.itemLink = rowData.itemLink
|
||||
end
|
||||
|
||||
ItemBrowserListCellMixin = {}
|
||||
|
||||
function ItemBrowserListCellMixin:OnLoad()
|
||||
end
|
||||
|
||||
function ItemBrowserListCellMixin:Init(dataProviderKey)
|
||||
self.dataProviderKey = dataProviderKey
|
||||
end
|
||||
|
||||
function ItemBrowserListCellMixin:Populate(rowData, dataIndex)
|
||||
local value = rowData[self.dataProviderKey]
|
||||
self.Text:SetJustifyH(self.textAlignment or "CENTER")
|
||||
self.Text:SetText(value or "?")
|
||||
end
|
||||
|
||||
ItemBrowserListCellNameIconMixin = CreateFromMixins(ItemBrowserListCellMixin)
|
||||
|
||||
function ItemBrowserListCellNameIconMixin:Populate(rowData, dataIndex)
|
||||
local textWidth = self:GetWidth() - 10
|
||||
|
||||
self.Text:SetText(rowData.name or UNKNOWN)
|
||||
self.Text:SetWidth(textWidth)
|
||||
self.SubText:SetWidth(textWidth)
|
||||
|
||||
if rowData.itemType and rowData.itemSubType then
|
||||
self.SubText:SetFormattedText("%s - %s", rowData.itemType, rowData.itemSubType)
|
||||
elseif rowData.itemType or rowData.itemSubType then
|
||||
self.SubText:SetText(rowData.itemType or rowData.itemSubType)
|
||||
else
|
||||
self.SubText:SetText("")
|
||||
end
|
||||
|
||||
self.Icon:SetTexture(rowData.icon or QUESTION_MARK_ICON)
|
||||
local r, g, b = GetItemQualityColor(rowData.rarity or 1)
|
||||
self.Border:SetVertexColor(r, g, b)
|
||||
end
|
||||
|
||||
ItemBrowserListCellItemLevelMixin = CreateFromMixins(ItemBrowserListCellMixin)
|
||||
|
||||
function ItemBrowserListCellItemLevelMixin:Populate(rowData, dataIndex)
|
||||
self.Text:SetText(rowData.itemLevel and rowData.itemLevel ~= 0 and rowData.itemLevel or "")
|
||||
end
|
||||
|
||||
ItemBrowserListCellItemIDMixin = CreateFromMixins(ItemBrowserListCellMixin)
|
||||
|
||||
function ItemBrowserListCellItemIDMixin:Populate(rowData, dataIndex)
|
||||
self.Text:SetText(rowData.itemID)
|
||||
end
|
||||
@@ -0,0 +1,331 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\..\FrameXML\UI.xsd">
|
||||
<Script file="Custom_ItemBrowser.lua"/>
|
||||
|
||||
<Button name="ItemBrowserListHeaderButtonTemplate" virtual="true">
|
||||
<Size x="50" y="30"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentBackground" parentKey="Background" setAllPoints="true">
|
||||
<Color r="0.2" g="0.2" b="0.2"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<!--
|
||||
<Layer level="HIGHLIGHT">
|
||||
<Texture name="$parentHighlight" parentKey="Highlight" setAllPoints="true" alpha="0.2" alphaMode="ADD">
|
||||
<Color r="1" g="1" b="1"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
-->
|
||||
</Layers>
|
||||
|
||||
<NormalFont style="SystemFont_Shadow_Small"/>
|
||||
<PushedTextOffset x="0" y="0"/>
|
||||
<!--
|
||||
<PushedTextOffset x="1" y="-1"/>
|
||||
<ButtonText name="$parentButtonText" parentKey="ButtonText">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" x="0" y="0"/>
|
||||
</Anchors>
|
||||
</ButtonText>
|
||||
-->
|
||||
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Mixin(self, ItemBrowserListHeaderButtonMixin)
|
||||
</OnLoad>
|
||||
<!--
|
||||
<OnClick>
|
||||
self:OnClick(button)
|
||||
</OnClick>
|
||||
-->
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="ItemBrowserListRowButtonTemplate" virtual="true">
|
||||
<Size x="0" y="46"/>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" x="1" y="0"/>
|
||||
<Anchor point="RIGHT" x="-1" y="0"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentBackground" parentKey="Background" setAllPoints="true" inherits="_SearchBarLg"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<HighlightTexture file="Interface\Common\Search">
|
||||
<TexCoords left="0.001953" right="0.501953" top="0.234375" bottom="0.601562"/>
|
||||
</HighlightTexture>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Mixin(self, ItemBrowserListRowButton)
|
||||
self:OnLoad()
|
||||
</OnLoad>
|
||||
<OnEvent>
|
||||
self:OnEvent(event, ...)
|
||||
</OnEvent>
|
||||
<OnEnter>
|
||||
self:OnEnter()
|
||||
</OnEnter>
|
||||
<OnLeave>
|
||||
self:OnLeave()
|
||||
</OnLeave>
|
||||
<OnClick>
|
||||
self:OnClick()
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<Frame name="ItemBrowserListCellNameIconTemplate" virtual="true">
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="$parentBorder" parentKey="Border" file="Interface\Common\Search">
|
||||
<Size x="40" y="40"/>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" x="10" y="0"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0.755859" right="0.830078" top="0.234375" bottom="0.531250"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="$parentIcon" parentKey="Icon">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentBorder" x="1" y="-2"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="$parentBorder" x="-1" y="1"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<FontString name="$parentText" parentKey="Text" inherits="GameFontNormalLarge" justifyH="LEFT" maxLines="1">
|
||||
<Size x="0" y="0"/>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="$parentIcon" relativePoint="RIGHT" x="10" y="8"/>
|
||||
</Anchors>
|
||||
<Color r="0.96875" g="0.8984375" b="0.578125" a="1"/>
|
||||
</FontString>
|
||||
<FontString name="$parentSubText" parentKey="SubText" inherits="GameFontNormal" justifyH="LEFT">
|
||||
<Size x="400" y="0"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentText" relativePoint="BOTTOMLEFT" x="0" y="-5"/>
|
||||
</Anchors>
|
||||
<Color r="0.66796875" g="0.51171875" b="0.3359375" a="1"/>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Mixin(self, ItemBrowserListCellNameIconMixin)
|
||||
self:OnLoad()
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Frame name="ItemBrowserListCellItemLevelTemplate" virtual="true">
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentText" parentKey="Text" inherits="GameFontNormalLarge" maxLines="1">
|
||||
<Size x="0" y="0"/>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" x="0" y="0"/>
|
||||
</Anchors>
|
||||
<Color r="1" g="1" b="1"/>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Mixin(self, ItemBrowserListCellItemLevelMixin)
|
||||
self:OnLoad()
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Frame name="ItemBrowserListCellItemIDTemplate" virtual="true">
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentText" parentKey="Text" inherits="GameFontNormalLarge" maxLines="1">
|
||||
<Size x="0" y="0"/>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" x="0" y="0"/>
|
||||
</Anchors>
|
||||
<Color r="1" g="1" b="1"/>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Mixin(self, ItemBrowserListCellItemIDMixin)
|
||||
self:OnLoad()
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
<Frame name="ItemBrowser" inherits="PortraitFrameTemplate" toplevel="true" enableMouse="true" parent="UIParent" hidden="true">
|
||||
<Size x="800" y="496"/>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" x="0" y="0"/>
|
||||
</Anchors>
|
||||
|
||||
<Frames>
|
||||
<Frame name="$parentInset" inherits="InsetFrameTemplate" parentKey="inset">
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" x="-4" y="-60"/>
|
||||
<Anchor point="BOTTOMLEFT" x="4" y="5"/>
|
||||
</Anchors>
|
||||
</Frame>
|
||||
|
||||
<EditBox name="$parentSearchBox" parentKey="SearchBox" inherits="PKBT_EditBoxTemplate" letters="64">
|
||||
<Size x="130" y="36"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="60" y="-22"/>
|
||||
<Anchor point="TOPRIGHT" x="-10" y="-22"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="$parentSearchIcon" parentKey="searchIcon" file="Interface\Common\UI-Searchbox-Icon">
|
||||
<Size x="14" y="14"/>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" x="5" y="-2"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button name="$parentClearButton" parentKey="clearButton" hidden="true">
|
||||
<Size x="22" y="22"/>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" x="-5" y="-1"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture parentKey="texture" file="Interface\FriendsFrame\ClearBroadcastIcon" alpha="0.5" setAllPoints="true"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnEnter>
|
||||
self.texture:SetAlpha(1.0)
|
||||
</OnEnter>
|
||||
<OnLeave>
|
||||
self.texture:SetAlpha(0.5)
|
||||
</OnLeave>
|
||||
<OnMouseDown>
|
||||
if self:IsEnabled() == 1 then
|
||||
self.texture:SetPoint("TOPLEFT", self, "TOPLEFT", 1, -1)
|
||||
end
|
||||
</OnMouseDown>
|
||||
<OnMouseUp>
|
||||
self.texture:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0)
|
||||
</OnMouseUp>
|
||||
<OnClick>
|
||||
self:GetParent():OnClearClick(button)
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Mixin(self, ItemBrowserSearchBoxMixin)
|
||||
self:OnLoad()
|
||||
</OnLoad>
|
||||
<OnHide>
|
||||
self:OnHide()
|
||||
</OnHide>
|
||||
<OnTextChanged>
|
||||
self:OnTextChanged(userInput)
|
||||
</OnTextChanged>
|
||||
<OnEnterPressed>
|
||||
self:OnEnterPressed()
|
||||
</OnEnterPressed>
|
||||
<OnEditFocusLost>
|
||||
self:OnEditFocusLost()
|
||||
</OnEditFocusLost>
|
||||
<OnEditFocusGained function="SearchBoxTemplate_OnEditFocusGained"/>
|
||||
</Scripts>
|
||||
</EditBox>
|
||||
|
||||
<StatusBar name="$parentSearchProgressBar" parentKey="SearchProgressBar" minValue="0" maxValue="1" alpha="0">
|
||||
<Size x="0" y="6"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentSearchBox" relativePoint="BOTTOMLEFT" x="7" y="1"/>
|
||||
<Anchor point="TOPRIGHT" relativeTo="$parentSearchBox" relativePoint="BOTTOMRIGHT" x="-7" y="1"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture parentKey="Background">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"/>
|
||||
<Anchor point="BOTTOMRIGHT"/>
|
||||
</Anchors>
|
||||
<Color r="0" g="0" b="0" a="0.3"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||||
<BarColor r="0" g="1" b="0"/>
|
||||
</StatusBar>
|
||||
|
||||
<ScrollFrame name="$parentScroll" parentKey="Scroll" inherits="HybridScrollFrameTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentInset" relativePoint="TOPLEFT" x="10" y="-50"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="$parentInset" relativePoint="BOTTOMRIGHT" x="-36" y="10"/>
|
||||
</Anchors>
|
||||
<Frames>
|
||||
<Slider name="$parentScrollBar" parentKey="ScrollBar" inherits="PKBT_HybridScrollBarTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="TOPRIGHT" x="5" y="-25"/>
|
||||
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT" x="5" y="27"/>
|
||||
</Anchors>
|
||||
</Slider>
|
||||
</Frames>
|
||||
</ScrollFrame>
|
||||
|
||||
<Frame name="$parentHeaderHolder" parentKey="HeaderHolder">
|
||||
<Size x="0" y="30"/>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="$parentScroll" relativePoint="TOPLEFT" x="0" y="12"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="$parentScroll" relativePoint="TOPRIGHT" x="0" y="12"/>
|
||||
</Anchors>
|
||||
</Frame>
|
||||
|
||||
<Button name="$parentTab1" inherits="CharacterFrameTabButtonTemplate" text="ADVENTURE_JOURNAL" id="1" parentKey="tab1">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="$parent" relativePoint="BOTTOMLEFT" x="19" y="-30"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick function="Adventure_TabOnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="$parentTab2" inherits="CharacterFrameTabButtonTemplate" text="HEADHUNTING" id="2" parentKey="tab2">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="$parentTab1" relativePoint="RIGHT" x="-16" y="0"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick function="Adventure_TabOnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="$parentTab3" inherits="CharacterFrameTabButtonTemplate" text="HARDCORE_CHALLENGES_TITLE" id="3" parentKey="tab3">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="$parentTab2" relativePoint="RIGHT" x="-16" y="0"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick function="Adventure_TabOnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="$parentTab4" inherits="CharacterFrameTabButtonTemplate" text="ITEM_BROWSER" id="4" parentKey="tab4">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="$parentTab3" relativePoint="RIGHT" x="-16" y="0"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick function="Adventure_TabOnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Mixin(self, ItemBrowserMixin)
|
||||
self:OnLoad()
|
||||
</OnLoad>
|
||||
<OnShow>
|
||||
self:OnShow()
|
||||
</OnShow>
|
||||
<OnEvent>
|
||||
self:OnEvent(event, ...)
|
||||
</OnEvent>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
@@ -0,0 +1,483 @@
|
||||
do
|
||||
local tonumber = tonumber
|
||||
local unpack = unpack
|
||||
local bitband = bit.band
|
||||
local strsub = string.sub
|
||||
|
||||
local LJ_BUFFER = {}
|
||||
|
||||
local NO_CLASS_FILTER = 0
|
||||
local NO_SPEC_FILTER = 0
|
||||
|
||||
local function getPlayerFactionID()
|
||||
local factionGroup = UnitFactionGroup("player") or "NEUTRAL"
|
||||
if factionGroup == "Renegade" then
|
||||
return (C_FactionManager.GetFactionInfoOriginal())
|
||||
end
|
||||
return PLAYER_FACTION_GROUP[factionGroup]
|
||||
end
|
||||
|
||||
local function filterByFaction(factionID, playerFactionID)
|
||||
if factionID == PLAYER_FACTION_GROUP.Neutral then
|
||||
return true
|
||||
elseif factionID == playerFactionID then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function filterByClassAndSpecFilters(classID, specID, classFilter, specFilter)
|
||||
if classFilter and specFilter then
|
||||
if specFilter == 3 then
|
||||
specFilter = 4
|
||||
end
|
||||
|
||||
if classFilter == NO_CLASS_FILTER and specFilter == NO_SPEC_FILTER then
|
||||
return true
|
||||
elseif classFilter == classID and specFilter == NO_SPEC_FILTER then
|
||||
return true
|
||||
elseif classFilter == classID and bitband(specFilter, specID) == specFilter then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function sortItemSets(a, b)
|
||||
if a[4] == b[4] then
|
||||
if a[6] ~= b[6] then
|
||||
return a[6]
|
||||
end
|
||||
return (tonumber(strsub(a[3], 3, -1) or 0)) > (tonumber(strsub(b[3], 3, -1) or 0))
|
||||
end
|
||||
return a[4] > b[4]
|
||||
end
|
||||
|
||||
C_LootJournal = {}
|
||||
|
||||
function C_LootJournal.GenerateLootData(classFilter, specFilter)
|
||||
table.wipe(LJ_BUFFER)
|
||||
|
||||
local serverID = C_Service and C_Service.GetRealmID and C_Service.GetRealmID() or 0
|
||||
local playerFactionID = getPlayerFactionID()
|
||||
local itemSetSourceRealm = EJ_ITEMSET_SOURCE_REALM and EJ_ITEMSET_SOURCE_REALM[serverID]
|
||||
|
||||
for setIndex, itemSetData in pairs(EJ_ITEMSET_DATA) do
|
||||
local name, itemlevel, tierName, sourceID, classID, specID, isPVP, itemList, factionID, realmFlag = unpack(itemSetData, 1, 10)
|
||||
if not factionID then
|
||||
factionID = 0
|
||||
end
|
||||
|
||||
if filterByClassAndSpecFilters(classID, specID, classFilter or 0, specFilter or 0)
|
||||
and filterByFaction(factionID, playerFactionID)
|
||||
then
|
||||
if itemSetSourceRealm and itemSetSourceRealm[setIndex] then
|
||||
sourceID = itemSetSourceRealm[setIndex]
|
||||
end
|
||||
local sourceText = EJ_ITEMSET_SOURCE[sourceID] or ""
|
||||
|
||||
local numItems = type(itemList) == "table" and #itemList or 0
|
||||
local items = {}
|
||||
|
||||
for i = 1, numItems do
|
||||
local itemID = itemList[i]
|
||||
items[i] = {
|
||||
itemID = itemID,
|
||||
icon = GetItemIcon(itemID) or [[Interface\Icons\INV_Misc_QuestionMark]],
|
||||
}
|
||||
end
|
||||
|
||||
LJ_BUFFER[#LJ_BUFFER + 1] = {setIndex, name, tierName or "", itemlevel, sourceText, isPVP == 1, items, numItems}
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(LJ_BUFFER, sortItemSets)
|
||||
|
||||
return LJ_BUFFER
|
||||
end
|
||||
|
||||
function C_LootJournal.GetNumItemSets()
|
||||
return #LJ_BUFFER
|
||||
end
|
||||
|
||||
function C_LootJournal.GetItemSetInfo(index)
|
||||
if index and LJ_BUFFER[index] then
|
||||
return unpack(LJ_BUFFER[index])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local NO_SPEC_FILTER = 0;
|
||||
local NO_CLASS_FILTER = 0;
|
||||
local NO_INV_TYPE_FILTER = 0;
|
||||
|
||||
--===================================================================================================================================
|
||||
LootJournalItemsMixin = { };
|
||||
|
||||
function LootJournalItemsMixin:OnLoad()
|
||||
self.Background:SetAtlas("loottab-background", true)
|
||||
self:SetView(LOOT_JOURNAL_ITEM_SETS);
|
||||
local _, _, classID = UnitClass("player");
|
||||
local specIndex = C_Talent.GetCurrentSpecTabIndex()
|
||||
self:SetClassAndSpecFilters(classID, specIndex);
|
||||
end
|
||||
|
||||
function LootJournalItemsMixin:SetClassAndSpecFilters(classID, specID)
|
||||
self.classFilter = classID;
|
||||
self.specFilter = specID;
|
||||
end
|
||||
|
||||
function LootJournalItemsMixin:GetClassAndSpecFilters()
|
||||
return self.classFilter or NO_CLASS_FILTER, self.specFilter or NO_SPEC_FILTER;
|
||||
end
|
||||
|
||||
function LootJournalItemsMixin:SetView(view)
|
||||
if ( self.view == view ) then
|
||||
return;
|
||||
end
|
||||
|
||||
self.view = view;
|
||||
self.ItemSetsFrame:Show();
|
||||
end
|
||||
|
||||
function LootJournalItemsMixin:GetActiveList()
|
||||
return self.ItemSetsFrame;
|
||||
end
|
||||
|
||||
function LootJournalItemsMixin:Refresh()
|
||||
self:GetActiveList():Refresh();
|
||||
end
|
||||
|
||||
function LootJournalItemButtonTemplate_OnEnter(self)
|
||||
local listFrame = self:GetParent();
|
||||
while ( listFrame and not listFrame.ShowItemTooltip ) do
|
||||
listFrame = listFrame:GetParent();
|
||||
end
|
||||
if ( listFrame ) then
|
||||
listFrame:ShowItemTooltip(self);
|
||||
self:SetScript("OnUpdate", LootJournalItemButton_OnUpdate);
|
||||
self.UpdateTooltip = LootJournalItemButtonTemplate_OnEnter;
|
||||
end
|
||||
end
|
||||
|
||||
function LootJournalItemButtonTemplate_OnLeave(self)
|
||||
self.UpdateTooltip = nil;
|
||||
GameTooltip:Hide();
|
||||
self:SetScript("OnUpdate", nil);
|
||||
ResetCursor();
|
||||
end
|
||||
|
||||
function LootJournalItemButton_OnClick(self, button)
|
||||
if HandleModifiedItemClick(self.itemLink) then
|
||||
return
|
||||
end
|
||||
|
||||
LootJournal_OpenItemByEntry(self.itemID)
|
||||
end
|
||||
|
||||
--===================================================================================================================================
|
||||
LootJournalItemSetsMixin = {}
|
||||
|
||||
local LJ_ITEMSET_X_OFFSET = 10;
|
||||
local LJ_ITEMSET_Y_OFFSET = 10;
|
||||
local LJ_ITEMSET_BUTTON_SPACING = 13;
|
||||
local LJ_ITEMSET_BOTTOM_BUFFER = 4;
|
||||
|
||||
function LootJournalItemSetsMixin:Refresh()
|
||||
self.dirty = true;
|
||||
local offset = self.scrollBar:GetValue();
|
||||
if ( offset == 0 ) then
|
||||
self:UpdateList();
|
||||
else
|
||||
self.scrollBar:SetValue(0);
|
||||
end
|
||||
if ( self.ClassButton ) then
|
||||
self:UpdateClassButtonText();
|
||||
end
|
||||
end
|
||||
|
||||
function LootJournalItemSetsMixin:GetClassAndSpecFilters()
|
||||
return self:GetParent():GetClassAndSpecFilters();
|
||||
end
|
||||
|
||||
function LootJournalItemSetsMixin:UpdateClassButtonText()
|
||||
end
|
||||
|
||||
function LootJournalItemSetsMixin:ShowItemTooltip(button)
|
||||
-- itemLink may not be available
|
||||
if not button.itemLink then
|
||||
GameTooltip:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
GameTooltip:SetOwner(button, "ANCHOR_RIGHT");
|
||||
GameTooltip:SetHyperlink(button.itemLink);
|
||||
|
||||
if LootJournal_CanOpenItemByEntry(button.itemID) then
|
||||
GameTooltip:AddLine(LOOTJOURNAL_ITEM_CLICK_TO_OPEN_LOOT, 0, 0.8, 1)
|
||||
end
|
||||
|
||||
self.tooltipItemID = button.itemID;
|
||||
GameTooltip:Show();
|
||||
end
|
||||
|
||||
function LootJournalItemSetsMixin:CheckItemButtonTooltip(button)
|
||||
if ( GameTooltip:GetOwner() == button and self.tooltipItemID ~= button.itemID ) then
|
||||
self:ShowItemTooltip(button);
|
||||
end
|
||||
end
|
||||
|
||||
function LootJournalItemSetsMixin:GetClassFilter()
|
||||
local classFilter, specFilter = self:GetClassAndSpecFilters();
|
||||
return classFilter;
|
||||
end
|
||||
|
||||
function LootJournalItemSetsMixin:GetSpecFilter()
|
||||
local classFilter, specFilter = self:GetClassAndSpecFilters();
|
||||
return specFilter;
|
||||
end
|
||||
|
||||
function LootJournalItemSetsMixin:SetClassAndSpecFilters(newClassFilter, newSpecFilter)
|
||||
local classFilter, specFilter = self:GetClassAndSpecFilters();
|
||||
if not self.classAndSpecFiltersSet or classFilter ~= newClassFilter or specFilter ~= newSpecFilter then
|
||||
--[[
|
||||
-- if choosing just a class without a spec, pick a spec
|
||||
if newClassFilter ~= NO_CLASS_FILTER and newSpecFilter == NO_SPEC_FILTER then
|
||||
local _, _, classID = UnitClass("player");
|
||||
-- if player's class, choose active spec
|
||||
-- otherwise use 1st spec
|
||||
if classID == newClassFilter then
|
||||
local specIndex = C_Talent.GetCurrentSpecTabIndex()
|
||||
newSpecFilter = specIndex
|
||||
else
|
||||
newSpecFilter = 1
|
||||
end
|
||||
end
|
||||
--]]
|
||||
self:GetParent():SetClassAndSpecFilters(newClassFilter, newSpecFilter);
|
||||
self.scrollBar:SetValue(0);
|
||||
self:Refresh();
|
||||
end
|
||||
|
||||
CloseDropDownMenus(1);
|
||||
self.classAndSpecFiltersSet = true;
|
||||
end
|
||||
|
||||
do
|
||||
function LootJournalItemButton_OnUpdate(self)
|
||||
if GameTooltip:IsOwned(self) then
|
||||
if IsModifiedClick("DRESSUP") then
|
||||
ShowInspectCursor();
|
||||
else
|
||||
ResetCursor();
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function OpenClassFilterDropDown(self, level)
|
||||
if level then
|
||||
self:GetParent():GetActiveList():OpenClassFilterDropDown(level);
|
||||
end
|
||||
end
|
||||
|
||||
function LootJournalItemsClassDropDown_OnLoad(self)
|
||||
UIDropDownMenu_Initialize(self, OpenClassFilterDropDown, "MENU");
|
||||
end
|
||||
|
||||
local CLASS_DROPDOWN = 1;
|
||||
|
||||
function LootJournalItemSetsMixin:OpenClassFilterDropDown(level)
|
||||
local filterClassID = self:GetClassFilter();
|
||||
local filterSpecID = self:GetSpecFilter();
|
||||
local classDisplayName, classTag, classID;
|
||||
|
||||
local function SetClassAndSpecFilters(_, classFilter, specFilter)
|
||||
self:SetClassAndSpecFilters(classFilter, specFilter);
|
||||
end
|
||||
|
||||
local info = UIDropDownMenu_CreateInfo();
|
||||
|
||||
if UIDROPDOWNMENU_MENU_VALUE == CLASS_DROPDOWN then
|
||||
info.text = ALL_CLASSES;
|
||||
info.checked = filterClassID == NO_CLASS_FILTER;
|
||||
info.arg1 = NO_CLASS_FILTER;
|
||||
info.arg2 = NO_SPEC_FILTER;
|
||||
info.func = SetClassAndSpecFilters;
|
||||
UIDropDownMenu_AddButton(info, level);
|
||||
|
||||
local numClasses = GetNumClasses();
|
||||
for i = 1, numClasses do
|
||||
classDisplayName, classTag, classID = GetClassInfo(i);
|
||||
if classID ~= 10 then
|
||||
info.text = classDisplayName;
|
||||
info.checked = filterClassID == classID;
|
||||
info.arg1 = classID;
|
||||
info.arg2 = NO_SPEC_FILTER;
|
||||
info.func = SetClassAndSpecFilters;
|
||||
UIDropDownMenu_AddButton(info, level);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if level == 1 then
|
||||
info.text = CLASS;
|
||||
info.func = nil;
|
||||
info.notCheckable = true;
|
||||
info.hasArrow = true;
|
||||
info.value = CLASS_DROPDOWN;
|
||||
UIDropDownMenu_AddButton(info, level);
|
||||
|
||||
local classDisplayName, classTag, classID;
|
||||
if filterClassID ~= NO_CLASS_FILTER then
|
||||
classID = filterClassID;
|
||||
classDisplayName = GetClassInfo(classID)
|
||||
else
|
||||
classDisplayName, classTag, classID = UnitClass("player");
|
||||
end
|
||||
|
||||
info.text = classDisplayName;
|
||||
info.notCheckable = true;
|
||||
info.arg1 = nil;
|
||||
info.arg2 = nil;
|
||||
info.func = nil;
|
||||
info.hasArrow = false;
|
||||
UIDropDownMenu_AddButton(info, level);
|
||||
|
||||
info.notCheckable = nil;
|
||||
for i = 1, GetNumSpecializationsForClassID(classID) do
|
||||
local _, specName = GetSpecializationInfoForClassID(classID, i)
|
||||
info.leftPadding = 10;
|
||||
info.text = specName;
|
||||
info.checked = filterSpecID == i;
|
||||
info.arg1 = classID;
|
||||
info.arg2 = i;
|
||||
info.func = SetClassAndSpecFilters;
|
||||
UIDropDownMenu_AddButton(info, level);
|
||||
end
|
||||
|
||||
info.text = ALL_SPECS;
|
||||
info.leftPadding = 10;
|
||||
info.checked = (classID == filterClassID and filterSpecID == NO_SPEC_FILTER) or (filterClassID == NO_CLASS_FILTER and filterSpecID == NO_SPEC_FILTER);
|
||||
info.arg1 = classID;
|
||||
info.arg2 = NO_SPEC_FILTER;
|
||||
info.func = SetClassAndSpecFilters;
|
||||
UIDropDownMenu_AddButton(info, level);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function LootJournalItemSetsMixin:OnLoad()
|
||||
self.scrollBar.trackBG:Hide();
|
||||
self.update = LootJournalItemSetsMixin.UpdateList;
|
||||
HybridScrollFrame_CreateButtons(self, "LootJournalItemSetButtonTemplate", LJ_ITEMSET_X_OFFSET, -LJ_ITEMSET_Y_OFFSET, "TOPLEFT", nil, nil, -LJ_ITEMSET_BUTTON_SPACING);
|
||||
end
|
||||
|
||||
function LootJournalItemSetsMixin:OnShow()
|
||||
if not self.init then
|
||||
self.init = true;
|
||||
self:Refresh();
|
||||
end
|
||||
end
|
||||
|
||||
local function onItemLoaded(itemID)
|
||||
local self = EncounterJournal.LootJournalItems.ItemSetsFrame
|
||||
for i = 1, #self.buttons do
|
||||
local itemButtons = self.buttons[i].ItemButtons;
|
||||
for j = 1, #itemButtons do
|
||||
if ( itemButtons[j].itemID == itemID ) then
|
||||
self:ConfigureItemButton(itemButtons[j]);
|
||||
return;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function LootJournalItemSetsMixin:ConfigureItemButton(button)
|
||||
local _, itemLink, itemQuality, _, _, _, _, _, _, icon = C_Item.GetItemInfo(button.itemID, false, onItemLoaded, true);
|
||||
button.itemLink = itemLink;
|
||||
itemQuality = itemQuality or Enum.ItemQuality.Epic; -- sets are most likely rare
|
||||
if ( itemQuality == Enum.ItemQuality.Uncommon ) then
|
||||
button.Border:SetAtlas("loottab-set-itemborder-green", true);
|
||||
elseif ( itemQuality == Enum.ItemQuality.Rare ) then
|
||||
button.Border:SetAtlas("loottab-set-itemborder-blue", true);
|
||||
elseif ( itemQuality == Enum.ItemQuality.Epic ) then
|
||||
button.Border:SetAtlas("loottab-set-itemborder-purple", true);
|
||||
end
|
||||
|
||||
button.Icon:SetTexture(icon or "Interface\\ICONS\\temp")
|
||||
|
||||
button:GetParent().SetName:SetTextColor(GetItemQualityColor(itemQuality));
|
||||
self:CheckItemButtonTooltip(button);
|
||||
end
|
||||
|
||||
function LootJournalItemSetsMixin:UpdateList()
|
||||
if ( self.dirty ) then
|
||||
C_LootJournal.GenerateLootData(self:GetClassAndSpecFilters())
|
||||
self.dirty = nil;
|
||||
end
|
||||
|
||||
local buttons = self.buttons;
|
||||
local offset = HybridScrollFrame_GetOffset(self);
|
||||
|
||||
local numSets = C_LootJournal.GetNumItemSets()
|
||||
|
||||
for i = 1, #buttons do
|
||||
local button = buttons[i];
|
||||
local index = offset + i;
|
||||
if ( index <= numSets ) then
|
||||
local setIndex, name, tierName, itemlevel, sourceText, isPVP, items, numItems = C_LootJournal.GetItemSetInfo(index)
|
||||
|
||||
button:Show();
|
||||
button.SetName:SetText(name);
|
||||
button.ItemLevel:SetFormattedText(EJ_SET_ITEM_LEVEL, tierName, itemlevel);
|
||||
button.PVPIcon:SetShown(isPVP);
|
||||
|
||||
button.setIndex = setIndex
|
||||
button.tooltipText = sourceText
|
||||
button:CheckItemButtonTooltip()
|
||||
|
||||
for j = 1, numItems do
|
||||
local itemButton = button.ItemButtons[j];
|
||||
if ( not itemButton ) then
|
||||
itemButton = CreateFrame("BUTTON", string.format("$parentItemButton%i", j), button, "LootJournalItemSetItemButtonTemplate");
|
||||
button[string.format("$parentItemButton%i", j)] = itemButton
|
||||
itemButton:SetPoint("LEFT", button.ItemButtons[j-1], "RIGHT", 5, 0);
|
||||
end
|
||||
itemButton.Icon:SetTexture(items[j].icon);
|
||||
itemButton.itemID = items[j].itemID;
|
||||
itemButton:Show();
|
||||
self:ConfigureItemButton(itemButton);
|
||||
end
|
||||
for j = numItems + 1, #button.ItemButtons do
|
||||
button.ItemButtons[j].itemID = nil;
|
||||
button.ItemButtons[j]:Hide();
|
||||
end
|
||||
else
|
||||
button:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
local totalHeight = numSets * buttons[1]:GetHeight() + (numSets - 1) * LJ_ITEMSET_BUTTON_SPACING + LJ_ITEMSET_Y_OFFSET + LJ_ITEMSET_BOTTOM_BUFFER
|
||||
HybridScrollFrame_Update(self, totalHeight, self:GetHeight());
|
||||
end
|
||||
|
||||
LootJournalItemSetButtonMixin = {}
|
||||
|
||||
function LootJournalItemSetButtonMixin:OnEnter()
|
||||
if self.tooltipText then
|
||||
GameTooltip:SetOwner(self, "ANCHOR_TOP")
|
||||
GameTooltip:SetText(LOOTJOURNAL_SOURCE_TOOLTIP_HEAD, 1, 1, 1)
|
||||
GameTooltip:AddLine(self.tooltipText, nil, nil, nil, 1)
|
||||
self.tooltipSetIndex = self.setIndex
|
||||
GameTooltip:Show()
|
||||
else
|
||||
GameTooltip:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
function LootJournalItemSetButtonMixin:CheckItemButtonTooltip()
|
||||
if ( GameTooltip:GetOwner() == self and self.tooltipSetIndex ~= self.setIndex ) then
|
||||
self:OnEnter();
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,164 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\..\FrameXML\UI.xsd">
|
||||
<Script file="Custom_LootJournalItems.lua"/>
|
||||
|
||||
<Button name="LootJournalItemButtonTemplate" virtual="true">
|
||||
<Scripts>
|
||||
<OnEnter function="LootJournalItemButtonTemplate_OnEnter"/>
|
||||
<OnLeave function="LootJournalItemButtonTemplate_OnLeave"/>
|
||||
<OnClick function="LootJournalItemButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<Button name="LootJournalItemSetItemButtonTemplate" inherits="LootJournalItemButtonTemplate" virtual="true" hidden="true">
|
||||
<Size x="32" y="32"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentIcon" parentKey="Icon">
|
||||
<Size x="28" y="28"/>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="BORDER">
|
||||
<Texture parentKey="Border">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="$parentIcon" relativePoint="CENTER" x="20" y="1"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
self:SetParentArray("ItemButtons")
|
||||
self.Border:SetAtlas("loottab-set-itemborder-green", true)
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<Button name="LootJournalItemSetButtonTemplate" virtual="true">
|
||||
<Size x="744" y="41"/>
|
||||
<HitRectInsets left="34" right="390" top="1" bottom="1"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture parentKey="Background">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="0" y="0"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentSetName" parentKey="SetName" inherits="GameFontNormalMed2" maxLines="1" justifyH="LEFT">
|
||||
<Size x="320" y="0"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="38" y="-6"/>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parentItemLevel" parentKey="ItemLevel" inherits="GameFontNormal" text="Item Level" maxLines="1" justifyH="LEFT">
|
||||
<Size x="320" y="0"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentSetName" relativePoint="BOTTOMLEFT" x="0" y="-2"/>
|
||||
</Anchors>
|
||||
<Color r="0.718" g="0.561" b="0.416"/>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Frame name="$parentPVPIcon" parentKey="PVPIcon" hidden="true">
|
||||
<Size x="30" y="30"/>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativePoint="LEFT" x="0" y="2"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentIcon" parentKey="Icon" file="Interface\PVPFrame\Icons\prestige-icon-4"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnEnter>
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine(LOOTJOURNAL_PVPICON_TOOLTIP_HEAD, 1, 1, 1)
|
||||
GameTooltip:AddLine(LOOTJOURNAL_PVPICON_TOOLTIP, nil, nil, nil, 1)
|
||||
GameTooltip:Show()
|
||||
</OnEnter>
|
||||
<OnLeave function="GameTooltip_Hide"/>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Button name="$parentItemButton1" parentKey="ItemButton1" inherits="LootJournalItemSetItemButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" x="397" y="0"/>
|
||||
</Anchors>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Mixin(self, LootJournalItemSetButtonMixin)
|
||||
self.Background:SetAtlas("loottab-set-background", true)
|
||||
</OnLoad>
|
||||
<OnEnter>
|
||||
self:OnEnter()
|
||||
</OnEnter>
|
||||
<OnLeave function="GameTooltip_Hide"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<Frame parent="EncounterJournal" name="$parentLootJournalItems" parentKey="LootJournalItems" hidden="true">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentInset" x="4" y="-51" />
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="$parentInset" x="-4" y="0"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture parentKey="Background">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" x="0" y="4"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Frame name="LootJournalItemsClassDropDown" parentKey="ClassDropDown" inherits="UIDropDownMenuTemplate" hidden="true">
|
||||
<Scripts>
|
||||
<OnLoad function="LootJournalItemsClassDropDown_OnLoad"/>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<ScrollFrame name="$parentItemSetsFrame" parentKey="ItemSetsFrame" inherits="MinimalHybridScrollFrameTemplate" hidden="true">
|
||||
<Size x="762" y="332"/>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" x="0" y="3"/>
|
||||
</Anchors>
|
||||
<Frames>
|
||||
<Button parentKey="ClassButton" inherits="EJButtonTemplate" text="FILTER">
|
||||
<Size x="114" y="26"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" x="-12" y="35"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnMouseDown>
|
||||
EJButtonMixin.OnMouseDown(self, button);
|
||||
ToggleDropDownMenu(1, nil, self:GetParent():GetParent().ClassDropDown, self, 5, 0);
|
||||
PlaySound("igMainMenuOptionCheckBoxOn");
|
||||
</OnMouseDown>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Mixin(self, LootJournalItemSetsMixin)
|
||||
self:OnLoad()
|
||||
</OnLoad>
|
||||
<OnShow>
|
||||
self:OnShow()
|
||||
</OnShow>
|
||||
</Scripts>
|
||||
</ScrollFrame>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Mixin(self, LootJournalItemsMixin)
|
||||
self:OnLoad()
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</Ui>
|
||||
@@ -0,0 +1,605 @@
|
||||
local SHOW_RECOMMENDATIONS = true
|
||||
local DENSITY = "regular"
|
||||
local ACCENT = { r = 1.0, g = 0.84, b = 0.42 }
|
||||
|
||||
local ROMAN = { "I","II","III","IV","V","VI","VII","VIII","IX","X" }
|
||||
|
||||
local T = "Interface\\AddOns\\MoonWellClient\\EncounterJournal\\Textures\\PlayerGuide\\"
|
||||
|
||||
local BIOME_TEX = {
|
||||
fire = T .. "Biome-Fire",
|
||||
arcane = T .. "Biome-Arcane",
|
||||
storm = T .. "Biome-Storm",
|
||||
shadow = T .. "Biome-Shadow",
|
||||
nature = T .. "Biome-Nature",
|
||||
holy = T .. "Biome-Holy",
|
||||
}
|
||||
|
||||
local BADGE_TEX = {
|
||||
active = T .. "StageBadge-Active",
|
||||
done = T .. "StageBadge-Done",
|
||||
locked = T .. "StageBadge-Locked",
|
||||
}
|
||||
|
||||
local TYPE_ICON = {
|
||||
level = T .. "TypeIcon-Level",
|
||||
quest = T .. "TypeIcon-Quest",
|
||||
quest_chain = T .. "TypeIcon-QuestChain",
|
||||
dungeon = T .. "TypeIcon-Dungeon",
|
||||
raid = T .. "TypeIcon-Raid",
|
||||
boss = T .. "TypeIcon-Boss",
|
||||
achievement = T .. "TypeIcon-Achievement",
|
||||
reputation = T .. "TypeIcon-Reputation",
|
||||
profession = T .. "TypeIcon-Profession",
|
||||
item = T .. "TypeIcon-Item",
|
||||
currency = T .. "TypeIcon-Currency",
|
||||
custom = T .. "TypeIcon-Custom",
|
||||
done = T .. "TypeIcon-Done",
|
||||
locked = T .. "TypeIcon-Locked",
|
||||
}
|
||||
|
||||
local function showOrHide(frame, show)
|
||||
if not frame then return end
|
||||
if show then frame:Show() else frame:Hide() end
|
||||
end
|
||||
|
||||
local function getScrollBar(scrollFrame)
|
||||
if not scrollFrame then return nil end
|
||||
return scrollFrame.ScrollBar or _G[scrollFrame:GetName() .. "ScrollBar"]
|
||||
end
|
||||
|
||||
local function updateScrollBar(scrollFrame, contentHeight, contentWidth)
|
||||
if not scrollFrame then return end
|
||||
|
||||
local viewHeight = scrollFrame:GetHeight() or 0
|
||||
local viewWidth = scrollFrame:GetWidth() or 0
|
||||
local scrollBar = getScrollBar(scrollFrame)
|
||||
|
||||
if scrollFrame.ScrollChild and viewWidth > 0 then
|
||||
scrollFrame.ScrollChild:SetWidth(math.max(1, contentWidth or viewWidth))
|
||||
end
|
||||
|
||||
if scrollBar then
|
||||
if contentHeight and viewHeight > 0 and contentHeight > viewHeight + 2 then
|
||||
scrollBar:Show()
|
||||
else
|
||||
scrollBar:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function scrollFrameOnMouseWheel(scrollFrame, delta)
|
||||
if not scrollFrame then return end
|
||||
|
||||
if ScrollFrameTemplate_OnMouseWheel then
|
||||
ScrollFrameTemplate_OnMouseWheel(scrollFrame, delta)
|
||||
return
|
||||
end
|
||||
|
||||
local scrollBar = getScrollBar(scrollFrame)
|
||||
if scrollBar then
|
||||
local minValue, maxValue = scrollBar:GetMinMaxValues()
|
||||
local step = scrollBar:GetValueStep() or 20
|
||||
scrollBar:SetValue(math.min(maxValue, math.max(minValue, scrollBar:GetValue() - delta * step)))
|
||||
end
|
||||
end
|
||||
|
||||
local function hideScrollBar(scrollFrame)
|
||||
local scrollBar = getScrollBar(scrollFrame)
|
||||
if scrollBar then scrollBar:Hide() end
|
||||
end
|
||||
|
||||
local function getBodyChild()
|
||||
local body = PlayerGuideFrame and PlayerGuideFrame.BodyScroll
|
||||
return body and body.ScrollChild
|
||||
end
|
||||
|
||||
local resolveEncounterJournalInstanceID
|
||||
|
||||
local function getRecommendationArtwork(data)
|
||||
if type(data) ~= "table" then return nil end
|
||||
|
||||
local artwork = data.artwork or data.cover or data.coverImage or data.buttonImage or data.bgImage or data.icon
|
||||
if artwork and artwork ~= "" then return artwork end
|
||||
|
||||
if not resolveEncounterJournalInstanceID or not EJ_GetInstanceInfo then return nil end
|
||||
local instanceID = resolveEncounterJournalInstanceID(data)
|
||||
if not instanceID then return nil end
|
||||
|
||||
local ok, _, _, bgImage, buttonImage, loreImage, buttonSmallImage = pcall(EJ_GetInstanceInfo, instanceID)
|
||||
if ok then
|
||||
return buttonImage or bgImage or loreImage or buttonSmallImage
|
||||
end
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────── Цепочка этапов ──
|
||||
|
||||
local stageBadges = {}
|
||||
|
||||
local function buildStageChain(stages)
|
||||
if not PlayerGuideFrame then return end
|
||||
local container = PlayerGuideFrame.StageChain
|
||||
|
||||
for i = (#stages + 1), #stageBadges do stageBadges[i]:Hide() end
|
||||
if #stages == 0 then return end
|
||||
|
||||
local width = container:GetWidth()
|
||||
if width == 0 then width = 600 end
|
||||
local slot = width / #stages
|
||||
|
||||
for i, s in ipairs(stages) do
|
||||
local badge = stageBadges[i]
|
||||
if not badge then
|
||||
badge = CreateFrame("Frame", "PlayerGuideStageBadge"..i, container, "MW_PG_StageBadgeTemplate")
|
||||
stageBadges[i] = badge
|
||||
end
|
||||
badge:ClearAllPoints()
|
||||
badge:SetPoint("LEFT", container, "LEFT", (i - 0.5) * slot - 19, 0)
|
||||
badge:Show()
|
||||
|
||||
badge.Roman:SetText(ROMAN[s.id] or tostring(s.id))
|
||||
badge.Label:SetText(s.short or s.name or "")
|
||||
|
||||
local status = s.status or "locked"
|
||||
badge.BadgeBG:SetTexture(BADGE_TEX[status] or BADGE_TEX.locked)
|
||||
if status == "active" then
|
||||
badge.Roman:SetTextColor(1.0, 0.91, 0.63)
|
||||
badge.Label:SetTextColor(1.0, 0.84, 0.42)
|
||||
elseif status == "done" then
|
||||
badge.Roman:SetText("v")
|
||||
badge.Roman:SetTextColor(0.79, 0.65, 0.38)
|
||||
badge.Label:SetTextColor(0.79, 0.65, 0.38)
|
||||
else
|
||||
badge.Roman:SetTextColor(0.40, 0.34, 0.22)
|
||||
badge.Label:SetTextColor(0.40, 0.34, 0.22)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────── Карточки целей ──
|
||||
|
||||
local objectiveCards = {}
|
||||
local CARD_HEIGHT = { compact = 54, regular = 70, cozy = 82 }
|
||||
local CARD_GAP = { compact = 6, regular = 8, cozy = 10 }
|
||||
|
||||
local function renderObjectives()
|
||||
local section = PlayerGuideFrame.ObjectivesSection
|
||||
local parent = section.ScrollFrame.ScrollChild
|
||||
local n = C_PlayerGuide.GetNumObjectives()
|
||||
local width = 0
|
||||
if PlayerGuideFrame.BodyScroll then
|
||||
width = PlayerGuideFrame.BodyScroll:GetWidth()
|
||||
end
|
||||
if width == 0 then width = section:GetWidth() end
|
||||
if width == 0 then width = section.ScrollFrame:GetWidth() end
|
||||
if width == 0 then width = 860 end
|
||||
-- Body child right inset 8, section right inset 0, scrollframe left/right insets 6/8.
|
||||
-- The card then keeps 8px equal visual padding on both sides of the objective row.
|
||||
parent:SetWidth(math.max(1, width - 30))
|
||||
|
||||
for i = (n + 1), #objectiveCards do objectiveCards[i]:Hide() end
|
||||
|
||||
local h = CARD_HEIGHT[DENSITY] or 44
|
||||
local gap = CARD_GAP[DENSITY] or 4
|
||||
local done = 0
|
||||
|
||||
for i = 1, n do
|
||||
local o = C_PlayerGuide.GetObjectiveInfo(i)
|
||||
if not o then break end
|
||||
|
||||
local card = objectiveCards[i]
|
||||
if not card then
|
||||
card = CreateFrame("Frame", "PlayerGuideObjective"..i, parent, "MW_PG_ObjectiveCardTemplate")
|
||||
objectiveCards[i] = card
|
||||
end
|
||||
card:ClearAllPoints()
|
||||
card:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, -(i - 1) * (h + gap))
|
||||
card:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, -(i - 1) * (h + gap))
|
||||
card:SetHeight(h)
|
||||
card.objectiveData = o
|
||||
card:Show()
|
||||
if card.SetBackdropColor then
|
||||
card:SetBackdropColor(0, 0, 0, 0.72)
|
||||
card:SetBackdropBorderColor(0.64, 0.45, 0.15, 0.95)
|
||||
end
|
||||
|
||||
card.TypeLabel:SetText(_G["MW_PG_TYPE_" .. string.upper(o.type or "custom")] or string.upper(o.type or ""))
|
||||
card.Title:SetText(o.title or "")
|
||||
card.Subtitle:SetText(o.subtitle or o.description or "")
|
||||
|
||||
card.ProgressBar:SetMinMaxValues(0, o.required or 1)
|
||||
card.ProgressBar:SetValue(o.progress or 0)
|
||||
card.Count:SetText(string.format("%d / %d", o.progress or 0, o.required or 1))
|
||||
|
||||
if o.completed then
|
||||
done = done + 1
|
||||
card.Icon:SetTexture(TYPE_ICON.done)
|
||||
card.Icon:SetTexCoord(0, 1, 0, 1)
|
||||
card.Title:SetTextColor(0.62, 0.84, 0.47)
|
||||
card.TypeLabel:SetTextColor(0.49, 0.65, 0.35)
|
||||
card.Count:SetTextColor(0.62, 0.84, 0.47)
|
||||
card.ProgressBar:SetStatusBarColor(0.55, 0.85, 0.40)
|
||||
card.IconBorder:SetVertexColor(0.40, 0.65, 0.30, 0.7)
|
||||
if card.SetBackdropBorderColor then
|
||||
card:SetBackdropBorderColor(0.26, 0.52, 0.18, 0.9)
|
||||
end
|
||||
else
|
||||
card.Icon:SetTexture(TYPE_ICON[o.type] or TYPE_ICON.custom)
|
||||
card.Icon:SetTexCoord(0, 1, 0, 1)
|
||||
card.Title:SetTextColor(1.0, 0.91, 0.63)
|
||||
card.TypeLabel:SetTextColor(0.63, 0.46, 0.19)
|
||||
card.Count:SetTextColor(0.79, 0.65, 0.38)
|
||||
card.ProgressBar:SetStatusBarColor(ACCENT.r, ACCENT.g, ACCENT.b)
|
||||
card.IconBorder:SetVertexColor(ACCENT.r, ACCENT.g, ACCENT.b, 0.55)
|
||||
end
|
||||
|
||||
if o.required == 1 and (o.progress or 0) == 0 then
|
||||
card.ProgressBar:Hide()
|
||||
else
|
||||
card.ProgressBar:Show()
|
||||
end
|
||||
end
|
||||
|
||||
parent:SetHeight(math.max(1, n * h + math.max(0, n - 1) * gap))
|
||||
section:SetHeight(48 + parent:GetHeight())
|
||||
hideScrollBar(section.ScrollFrame)
|
||||
section.Count:SetText(string.format(" %d / %d", done, n))
|
||||
end
|
||||
|
||||
-- ─────────────────────────────── Карточки рекомендаций ──
|
||||
|
||||
local recCards = {}
|
||||
|
||||
local function renderRecommendations()
|
||||
local section = PlayerGuideFrame.RecommendationsSection
|
||||
if not SHOW_RECOMMENDATIONS then section:Hide(); return end
|
||||
|
||||
local parent = section.ScrollFrame.ScrollChild
|
||||
local n = C_PlayerGuide.GetNumRecommendations()
|
||||
if n == 0 then section:Hide(); return end
|
||||
section:Show()
|
||||
|
||||
for i = (n + 1), #recCards do recCards[i]:Hide() end
|
||||
|
||||
local cardW = 176
|
||||
local gap = 16
|
||||
|
||||
for i = 1, n do
|
||||
local r = C_PlayerGuide.GetRecommendationInfo(i)
|
||||
if not r then break end
|
||||
|
||||
local card = recCards[i]
|
||||
if not card then
|
||||
card = CreateFrame("Button", "PlayerGuideRec"..i, parent, "MW_PG_RecCardTemplate")
|
||||
recCards[i] = card
|
||||
end
|
||||
card:ClearAllPoints()
|
||||
card:SetPoint("TOPLEFT", parent, "TOPLEFT", (i - 1) * (cardW + gap), 0)
|
||||
card.recData = r
|
||||
card:Show()
|
||||
if card.SetBackdropColor then
|
||||
card:SetBackdropColor(0, 0, 0, 0.88)
|
||||
card:SetBackdropBorderColor(0.64, 0.45, 0.15, 0.95)
|
||||
end
|
||||
|
||||
local artwork = getRecommendationArtwork(r)
|
||||
card.Artwork:SetTexCoord(0, 0.68359375, 0, 0.7421875)
|
||||
card.Artwork:SetVertexColor(1, 1, 1, 1)
|
||||
if not artwork or not card.Artwork:SetTexture(artwork) then
|
||||
card.Artwork:SetTexture(BIOME_TEX[r.biome] or BIOME_TEX.fire)
|
||||
end
|
||||
card.Name:SetText(r.name or r.title or "")
|
||||
card.Loc:SetText(r.loc or r.description or "")
|
||||
card.Level:SetText(r.level or "")
|
||||
end
|
||||
|
||||
parent:SetWidth(math.max(1, n * cardW + math.max(0, n - 1) * gap))
|
||||
parent:SetHeight(116)
|
||||
section:SetHeight(162)
|
||||
hideScrollBar(section.ScrollFrame)
|
||||
section.Count:SetText(" " .. n)
|
||||
end
|
||||
|
||||
local function updateBodyContent()
|
||||
local F = PlayerGuideFrame
|
||||
local child = getBodyChild()
|
||||
if not F or not child then return end
|
||||
|
||||
local width = F.BodyScroll:GetWidth()
|
||||
if width == 0 then width = 860 end
|
||||
child:SetWidth(math.max(1, width - 8))
|
||||
|
||||
local y = 0
|
||||
F.ObjectivesSection:ClearAllPoints()
|
||||
F.ObjectivesSection:SetPoint("TOPLEFT", child, "TOPLEFT", 0, y)
|
||||
F.ObjectivesSection:SetPoint("TOPRIGHT", child, "TOPRIGHT", 0, y)
|
||||
y = y - F.ObjectivesSection:GetHeight() - 16
|
||||
|
||||
if F.RecommendationsSection:IsShown() then
|
||||
F.RecommendationsSection:ClearAllPoints()
|
||||
F.RecommendationsSection:SetPoint("TOPLEFT", child, "TOPLEFT", 0, y)
|
||||
F.RecommendationsSection:SetPoint("TOPRIGHT", child, "TOPRIGHT", 0, y)
|
||||
y = y - F.RecommendationsSection:GetHeight()
|
||||
end
|
||||
|
||||
local contentHeight = math.max(1, -y + 8)
|
||||
child:SetHeight(contentHeight)
|
||||
updateScrollBar(F.BodyScroll, contentHeight, child:GetWidth())
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────── Layout ──
|
||||
|
||||
local function applyLayout(self, hasChain)
|
||||
local chain = self.StageChain
|
||||
local hdr = self.Header
|
||||
local body = self.BodyScroll
|
||||
|
||||
chain:ClearAllPoints()
|
||||
chain:SetPoint("TOPLEFT", self, "TOPLEFT", 28, -8)
|
||||
chain:SetPoint("TOPRIGHT", self, "TOPRIGHT", -28, -8)
|
||||
|
||||
hdr:ClearAllPoints()
|
||||
if hasChain then
|
||||
hdr:SetPoint("TOPLEFT", chain, "BOTTOMLEFT", 0, -6)
|
||||
hdr:SetPoint("TOPRIGHT", chain, "BOTTOMRIGHT", 0, -6)
|
||||
else
|
||||
hdr:SetPoint("TOPLEFT", self, "TOPLEFT", 30, -68)
|
||||
hdr:SetPoint("TOPRIGHT", self, "TOPRIGHT", -30, -68)
|
||||
end
|
||||
|
||||
body:ClearAllPoints()
|
||||
body:SetPoint("TOPLEFT", hdr, "BOTTOMLEFT", -4, -8)
|
||||
body:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -30, 16)
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────── Show/hide ──
|
||||
|
||||
local function showState(state)
|
||||
local F = PlayerGuideFrame
|
||||
local hasData = (state == "data")
|
||||
-- StageChain visibility controlled in refresh() based on #stages
|
||||
showOrHide(F.Header, hasData)
|
||||
showOrHide(F.BodyScroll, hasData)
|
||||
showOrHide(F.ObjectivesSection, hasData)
|
||||
showOrHide(F.RecommendationsSection, hasData and SHOW_RECOMMENDATIONS)
|
||||
showOrHide(F.EmptyState, state == "empty")
|
||||
showOrHide(F.LoadingState, state == "loading")
|
||||
if not hasData then showOrHide(F.StageChain, false) end
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────── Render ──
|
||||
|
||||
local function refresh()
|
||||
if not PlayerGuideFrame then return end
|
||||
|
||||
local state = C_PlayerGuide.GetState()
|
||||
showState(state)
|
||||
if state ~= "data" then return end
|
||||
|
||||
local id, name, desc, progress = C_PlayerGuide.GetStageInfo()
|
||||
local stages = C_PlayerGuide.GetStages() or {}
|
||||
|
||||
local F = PlayerGuideFrame
|
||||
if #stages > 0 then
|
||||
F.Header.Pretitle:SetText(string.format(
|
||||
MW_PLAYER_GUIDE_STAGE_OF or "ЭТАП %d ИЗ %d", id or 0, #stages))
|
||||
else
|
||||
F.Header.Pretitle:SetText(string.format("ЭТАП %d", id or 0))
|
||||
end
|
||||
F.Header.Title:SetText(name or "")
|
||||
F.Header.Description:SetText(desc or "")
|
||||
F.Header.ProgressLabel:SetText(MW_PLAYER_GUIDE_STAGE_PROGRESS or "ПРОГРЕСС ЭТАПА")
|
||||
F.Header.ProgressPercent:SetText((progress or 0) .. "%")
|
||||
F.Header.ProgressBar:SetMinMaxValues(0, 100)
|
||||
F.Header.ProgressBar:SetValue(progress or 0)
|
||||
|
||||
local hasChain = #stages > 0
|
||||
showOrHide(F.StageChain, hasChain)
|
||||
applyLayout(F, hasChain)
|
||||
buildStageChain(stages)
|
||||
updateBodyContent()
|
||||
renderObjectives()
|
||||
renderRecommendations()
|
||||
updateBodyContent()
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────── XML callbacks ──
|
||||
|
||||
function MW_PlayerGuide_OnLoad(self)
|
||||
EncounterJournal.playerGuideFrame = self
|
||||
self:EnableMouse(true)
|
||||
self:EnableMouseWheel(true)
|
||||
|
||||
local bodyChild = self.BodyScroll and self.BodyScroll.ScrollChild
|
||||
if bodyChild then
|
||||
self.ObjectivesSection:SetParent(bodyChild)
|
||||
self.RecommendationsSection:SetParent(bodyChild)
|
||||
end
|
||||
|
||||
applyLayout(self, false)
|
||||
|
||||
local objScrollBar = getScrollBar(self.ObjectivesSection.ScrollFrame)
|
||||
if objScrollBar then objScrollBar:Hide() end
|
||||
local recScrollBar = getScrollBar(self.RecommendationsSection.ScrollFrame)
|
||||
if recScrollBar then recScrollBar:Hide() end
|
||||
hideScrollBar(self.BodyScroll)
|
||||
|
||||
C_PlayerGuide.RegisterListener(function() refresh() end)
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_OnMouseWheel(self, delta)
|
||||
if self.BodyScroll and self.BodyScroll:IsShown() then
|
||||
scrollFrameOnMouseWheel(self.BodyScroll, delta)
|
||||
end
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_OnShow()
|
||||
if not C_PlayerGuide.HasData() then
|
||||
C_PlayerGuide.SetLoading()
|
||||
C_PlayerGuide.RequestRefresh()
|
||||
end
|
||||
refresh()
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_RefreshButton_OnClick()
|
||||
C_PlayerGuide.SetLoading()
|
||||
C_PlayerGuide.RequestRefresh()
|
||||
end
|
||||
|
||||
local function validEncounterJournalInstanceID(value)
|
||||
local instanceID = tonumber(value)
|
||||
if not instanceID or instanceID <= 0 or not EJ_GetInstanceInfo then return nil end
|
||||
|
||||
local ok, instanceName = pcall(EJ_GetInstanceInfo, instanceID)
|
||||
if ok and instanceName then return instanceID end
|
||||
end
|
||||
|
||||
local function mapIDToEncounterJournalInstanceID(value)
|
||||
local mapID = tonumber(value)
|
||||
if not mapID or mapID <= 0 then return nil end
|
||||
if not C_EncounterJournal or not C_EncounterJournal.GetInstanceIDByMapID then return nil end
|
||||
|
||||
local ok, instanceID = pcall(C_EncounterJournal.GetInstanceIDByMapID, mapID)
|
||||
if ok then
|
||||
return validEncounterJournalInstanceID(instanceID)
|
||||
end
|
||||
end
|
||||
|
||||
function resolveEncounterJournalInstanceID(data)
|
||||
if type(data) == "number" or type(data) == "string" then
|
||||
return validEncounterJournalInstanceID(data) or mapIDToEncounterJournalInstanceID(data)
|
||||
end
|
||||
if type(data) ~= "table" then return nil end
|
||||
|
||||
local instanceID =
|
||||
validEncounterJournalInstanceID(data.ej_instanceID) or
|
||||
validEncounterJournalInstanceID(data.ejInstanceID) or
|
||||
validEncounterJournalInstanceID(data.journalInstanceID) or
|
||||
validEncounterJournalInstanceID(data.instanceID) or
|
||||
validEncounterJournalInstanceID(data.dungeonID) or
|
||||
validEncounterJournalInstanceID(data.targetID)
|
||||
if instanceID then return instanceID end
|
||||
|
||||
return mapIDToEncounterJournalInstanceID(data.mapID) or
|
||||
mapIDToEncounterJournalInstanceID(data.worldMapAreaID) or
|
||||
mapIDToEncounterJournalInstanceID(data.instanceID) or
|
||||
mapIDToEncounterJournalInstanceID(data.dungeonID) or
|
||||
mapIDToEncounterJournalInstanceID(data.targetID)
|
||||
end
|
||||
|
||||
local function requestOpenTarget(data)
|
||||
if type(data) == "table" and data.id and C_PlayerGuide and C_PlayerGuide.RequestOpenTarget then
|
||||
C_PlayerGuide.RequestOpenTarget(data.id)
|
||||
end
|
||||
end
|
||||
|
||||
local function openEncounterJournalInstance(data, encounterID)
|
||||
if not EncounterJournal then return nil end
|
||||
|
||||
local instanceID = resolveEncounterJournalInstanceID(data)
|
||||
if not instanceID then
|
||||
requestOpenTarget(data)
|
||||
return nil
|
||||
end
|
||||
|
||||
if type(data) == "table" and not encounterID then
|
||||
encounterID = data.encounterID
|
||||
end
|
||||
|
||||
if not EncounterJournal:IsShown() then
|
||||
ShowUIPanel(EncounterJournal)
|
||||
end
|
||||
|
||||
if EncounterJournal.encounter then
|
||||
EncounterJournal.encounter:Show()
|
||||
end
|
||||
if EncounterJournal.instanceSelect then
|
||||
EJ_ContentTab_SelectAppropriateInstanceTab(instanceID)
|
||||
end
|
||||
|
||||
if NavBar_Reset and EncounterJournal.navBar then
|
||||
NavBar_Reset(EncounterJournal.navBar)
|
||||
end
|
||||
|
||||
EncounterJournal_DisplayInstance(instanceID)
|
||||
|
||||
if encounterID and encounterID > 0 then
|
||||
EncounterJournal_DisplayEncounter(encounterID)
|
||||
end
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_Objective_OnClick(self)
|
||||
local o = self.objectiveData
|
||||
if not o then return end
|
||||
|
||||
if o.type == "dungeon" or o.type == "raid" then
|
||||
openEncounterJournalInstance(o)
|
||||
elseif o.type == "boss" then
|
||||
if o.encounterID and o.encounterID > 0 then
|
||||
if not EncounterJournal:IsShown() then ShowUIPanel(EncounterJournal) end
|
||||
EncounterJournal_DisplayEncounter(o.encounterID)
|
||||
end
|
||||
elseif o.type == "achievement" then
|
||||
if o.achievementID then
|
||||
if AchievementFrame_LoadUI then AchievementFrame_LoadUI() end
|
||||
if AchievementFrame_SelectAchievement then
|
||||
AchievementFrame_SelectAchievement(o.achievementID)
|
||||
end
|
||||
end
|
||||
elseif o.type == "item" then
|
||||
if o.itemID and o.itemID > 0 then
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetItemByID(o.itemID)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
elseif o.type == "quest" or o.type == "quest_chain" then
|
||||
local qid = o.questID or o.questChainID or o.targetID
|
||||
if qid and qid > 0 then
|
||||
pcall(SetItemRef, "quest:" .. qid .. ":-1", "", "LeftButton")
|
||||
end
|
||||
else
|
||||
C_PlayerGuide.RequestOpenTarget(o.id)
|
||||
end
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_Objective_OnEnter(self)
|
||||
local o = self.objectiveData
|
||||
if not o then return end
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine(o.title or "", 1, 0.91, 0.63)
|
||||
if o.subtitle and o.subtitle ~= "" then
|
||||
GameTooltip:AddLine(o.subtitle, 0.66, 0.59, 0.47, true)
|
||||
end
|
||||
if not o.completed then
|
||||
GameTooltip:AddLine(
|
||||
string.format("Прогресс: %d / %d", o.progress or 0, o.required or 1),
|
||||
0.79, 0.65, 0.38)
|
||||
end
|
||||
GameTooltip:Show()
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_Objective_OnLeave() GameTooltip:Hide() end
|
||||
|
||||
function MW_PlayerGuide_Rec_OnClick(self)
|
||||
local r = self.recData
|
||||
if r then openEncounterJournalInstance(r) end
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_Rec_OnEnter(self)
|
||||
local r = self.recData
|
||||
if not r then return end
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine(r.name or r.title or "", 1, 0.91, 0.63)
|
||||
if r.loc then GameTooltip:AddLine(r.loc, 0.66, 0.59, 0.47) end
|
||||
if r.level then GameTooltip:AddLine("Уровень: " .. r.level, 0.79, 0.65, 0.38) end
|
||||
GameTooltip:Show()
|
||||
end
|
||||
|
||||
function MW_PlayerGuide_Rec_OnLeave() GameTooltip:Hide() end
|
||||
|
||||
-- ─────────────────────────────── Экспорт для отладки ──
|
||||
|
||||
MW_PlayerGuide = {
|
||||
Refresh = refresh,
|
||||
InjectDevData = function() C_PlayerGuide._InjectDevPayload() end,
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\..\FrameXML\UI.xsd">
|
||||
|
||||
<Script file="PlayerGuide.lua"/>
|
||||
|
||||
<!-- ───────── Шрифты ───────── -->
|
||||
|
||||
<Font name="MW_PG_Title" inherits="GameFontNormalHuge" virtual="true">
|
||||
<Color r="1.0" g="0.84" b="0.42"/>
|
||||
<Shadow><Color r="0" g="0" b="0"/><Offset><AbsDimension x="1" y="-1"/></Offset></Shadow>
|
||||
</Font>
|
||||
|
||||
<Font name="MW_PG_Section" inherits="QuestFont_Large" virtual="true">
|
||||
<Color r="1.0" g="0.84" b="0.42"/>
|
||||
<Shadow><Color r="0" g="0" b="0"/><Offset><AbsDimension x="1" y="-1"/></Offset></Shadow>
|
||||
</Font>
|
||||
|
||||
<Font name="MW_PG_Body" inherits="GameFontHighlight" virtual="true">
|
||||
<Color r="0.85" g="0.78" b="0.63"/>
|
||||
</Font>
|
||||
|
||||
<Font name="MW_PG_Caps" inherits="GameFontNormalSmall" virtual="true">
|
||||
<Color r="0.63" g="0.46" b="0.19"/>
|
||||
</Font>
|
||||
|
||||
<Font name="MW_PG_Italic" inherits="QuestFont" virtual="true">
|
||||
<Color r="0.66" g="0.59" b="0.47"/>
|
||||
</Font>
|
||||
|
||||
<!-- ───────── Карточка цели ───────── -->
|
||||
<!-- Высота = 60px (DENSITY "regular"). Позиции: TypeLabel+Title строка 1, Subtitle строка 2, бар внизу. -->
|
||||
|
||||
<Frame name="MW_PG_ObjectiveCardTemplate" virtual="true">
|
||||
<Size><AbsDimension x="0" y="70"/></Size>
|
||||
<Backdrop bgFile="Interface\Buttons\WHITE8X8"
|
||||
edgeFile="Interface\Buttons\WHITE8X8"
|
||||
tile="true">
|
||||
<BackgroundInsets><AbsInset left="0" right="0" top="0" bottom="0"/></BackgroundInsets>
|
||||
<EdgeSize><AbsValue val="1"/></EdgeSize>
|
||||
<TileSize><AbsValue val="64"/></TileSize>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<!-- Рамка иконки — фоновый слой, центрируется по LEFT -->
|
||||
<Texture name="$parentIconBorder" parentKey="IconBorder" file="Interface\Buttons\UI-Quickslot2">
|
||||
<Size><AbsDimension x="50" y="50"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<TexCoords left="0.2" right="0.8" top="0.2" bottom="0.8"/>
|
||||
<Color r="1.0" g="0.84" b="0.42" a="0.55"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="$parentIcon" parentKey="Icon" file="Interface\Icons\INV_Misc_QuestionMark">
|
||||
<Size><AbsDimension x="34" y="34"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT"><Offset><AbsDimension x="20" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<!-- Строка 1: TypeLabel (префикс) + Title (основной текст) на одной строке -->
|
||||
<FontString name="$parentTypeLabel" parentKey="TypeLabel" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Size><AbsDimension x="90" y="12"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="72" y="-16"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parentTitle" parentKey="Title" inherits="GameFontNormal" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="185" y="-16"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-118" y="-16"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<!-- Строка 2: Subtitle (описание/прогресс) -->
|
||||
<FontString name="$parentSubtitle" parentKey="Subtitle" inherits="MW_PG_Italic" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="72" y="-36"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-118" y="-36"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<!-- Count: мелко снизу справа -->
|
||||
<FontString name="$parentCount" parentKey="Count" inherits="GameFontNormalSmall" justifyH="RIGHT">
|
||||
<Size><AbsDimension x="110" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-18" y="10"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="0.63" g="0.46" b="0.19"/>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- Прогресс-бар: тонкая полоска у самого низа карточки -->
|
||||
<StatusBar name="$parentProgressBar" parentKey="ProgressBar">
|
||||
<Size><AbsDimension x="0" y="4"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="72" y="8"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-118" y="8"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<BarTexture file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-Fill"/>
|
||||
<BarColor r="1.0" g="0.79" b="0.29"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture setAllPoints="true"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-BG"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</StatusBar>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnClick function="MW_PlayerGuide_Objective_OnClick"/>
|
||||
<OnEnter function="MW_PlayerGuide_Objective_OnEnter"/>
|
||||
<OnLeave function="MW_PlayerGuide_Objective_OnLeave"/>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
<!-- ───────── Карточка рекомендации ───────── -->
|
||||
|
||||
<Button name="MW_PG_RecCardTemplate" virtual="true">
|
||||
<!-- Height 96: fits in the 100px visible area of RecommendationsSection scrollframe -->
|
||||
<Size><AbsDimension x="176" y="116"/></Size>
|
||||
<Backdrop bgFile="Interface\Buttons\WHITE8X8"
|
||||
edgeFile="Interface\Buttons\WHITE8X8"
|
||||
tile="true">
|
||||
<BackgroundInsets><AbsInset left="0" right="0" top="0" bottom="0"/></BackgroundInsets>
|
||||
<EdgeSize><AbsValue val="1"/></EdgeSize>
|
||||
<TileSize><AbsValue val="64"/></TileSize>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<!-- Artwork: 138x56, top offset 5 → bottom at y=61 from card top -->
|
||||
<Texture name="$parentArtwork" parentKey="Artwork" file="Interface\Buttons\WHITE8X8" setAllPoints="true">
|
||||
<Color r="1" g="1" b="1"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<!-- Level: top-right corner of artwork (artwork starts at y=-5 → level at TOPRIGHT+(-4,-9)) -->
|
||||
<Texture name="$parentTextBackdrop" parentKey="TextBackdrop" file="Interface\Buttons\WHITE8X8">
|
||||
<Size><AbsDimension x="176" y="42"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM"/>
|
||||
</Anchors>
|
||||
<Color r="0" g="0" b="0" a="0.72"/>
|
||||
</Texture>
|
||||
<FontString name="$parentLevel" parentKey="Level" inherits="GameFontNormalSmall" justifyH="RIGHT">
|
||||
<Size><AbsDimension x="80" y="12"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-4" y="-9"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<!-- Name: 4px below artwork (artwork bottom y=61, name at y=65) -->
|
||||
<FontString name="$parentName" parentKey="Name" inherits="GameFontNormal" justifyH="LEFT">
|
||||
<Size><AbsDimension x="162" y="16"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-82"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<!-- Loc: 3px below name (name bottom y=79, loc at y=82) -->
|
||||
<FontString name="$parentLoc" parentKey="Loc" inherits="MW_PG_Italic" justifyH="LEFT">
|
||||
<Size><AbsDimension x="162" y="16"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-98"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnClick function="MW_PlayerGuide_Rec_OnClick"/>
|
||||
<OnEnter function="MW_PlayerGuide_Rec_OnEnter"/>
|
||||
<OnLeave function="MW_PlayerGuide_Rec_OnLeave"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- ───────── Бейдж этапа ───────── -->
|
||||
|
||||
<Frame name="MW_PG_StageBadgeTemplate" virtual="true">
|
||||
<Size><AbsDimension x="38" y="38"/></Size>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentBadgeBG" parentKey="BadgeBG"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\StageBadge-Locked">
|
||||
<Size><AbsDimension x="60" y="60"/></Size>
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString name="$parentRoman" parentKey="Roman" inherits="GameFontNormalLarge" justifyH="CENTER">
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<FontString name="$parentLabel" parentKey="Label" inherits="MW_PG_Caps" justifyH="CENTER">
|
||||
<Size><AbsDimension x="100" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
|
||||
<!-- ───────── Корневой фрейм ───────── -->
|
||||
|
||||
<Frame name="PlayerGuideFrame" hidden="true" parent="EncounterJournal">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="EncounterJournalInset" relativePoint="TOPLEFT">
|
||||
<Offset><AbsDimension x="5" y="-4"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="EncounterJournalInset" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="-3" y="0"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture parentKey="BackgroundPage"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Background-Page"
|
||||
setAllPoints="true">
|
||||
<TexCoords left="0" right="1" top="0" bottom="1"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="BORDER">
|
||||
<Texture parentKey="TabDivider"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Section-Divider">
|
||||
<Size><AbsDimension x="0" y="16"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="28" y="-52"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-28" y="-52"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
|
||||
<Frames>
|
||||
|
||||
<!-- 1) Цепочка этапов — позиция задаётся Lua в OnLoad -->
|
||||
<Frame name="PlayerGuideStageChain" parentKey="StageChain">
|
||||
<Size><AbsDimension x="0" y="62"/></Size>
|
||||
</Frame>
|
||||
|
||||
<!-- 2) Заголовок этапа — позиция задаётся Lua в OnLoad -->
|
||||
<Frame name="PlayerGuideHeader" parentKey="Header">
|
||||
<Size><AbsDimension x="0" y="82"/></Size>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString name="PlayerGuideHeaderPretitle" parentKey="Pretitle" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="PlayerGuideHeaderTitle" parentKey="Title" inherits="MW_PG_Title" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="-18"/></Offset></Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-260" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="PlayerGuideHeaderDesc" parentKey="Description" inherits="MW_PG_Body" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="4" y="-48"/></Offset></Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-260" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="PlayerGuideHeaderProgLabel" parentKey="ProgressLabel" inherits="MW_PG_Caps" justifyH="RIGHT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="PlayerGuideHeaderProgPct" parentKey="ProgressPercent" inherits="MW_PG_Title" justifyH="RIGHT">
|
||||
<Size><AbsDimension x="240" y="28"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="-14"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<StatusBar name="PlayerGuideHeaderProgBar" parentKey="ProgressBar">
|
||||
<Size><AbsDimension x="240" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="-52"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<BarTexture file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-Fill"/>
|
||||
<BarColor r="1.0" g="0.79" b="0.29"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture setAllPoints="true"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-BG"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</StatusBar>
|
||||
<Button name="PlayerGuideHeaderRefresh" parentKey="RefreshButton" inherits="UIPanelButtonTemplate2" text="MW_PLAYER_GUIDE_REFRESH">
|
||||
<Size><AbsDimension x="100" y="22"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="-72"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
local T = "Interface\\AddOns\\MoonWellClient\\EncounterJournal\\Textures\\PlayerGuide\\"
|
||||
self:SetNormalTexture(T.."Button-Up")
|
||||
self:SetPushedTexture(T.."Button-Down")
|
||||
self:SetHighlightTexture(T.."Button-Highlight")
|
||||
</OnLoad>
|
||||
<OnClick function="MW_PlayerGuide_RefreshButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 3) Задачи этапа — позиция задаётся Lua в OnLoad -->
|
||||
<ScrollFrame name="PlayerGuideBodyScroll" parentKey="BodyScroll" inherits="UIPanelScrollFrameTemplate">
|
||||
<ScrollChild>
|
||||
<Frame parentKey="ScrollChild">
|
||||
<Size><AbsDimension x="860" y="1"/></Size>
|
||||
</Frame>
|
||||
</ScrollChild>
|
||||
</ScrollFrame>
|
||||
|
||||
<Frame name="PlayerGuideObjSection" parentKey="ObjectivesSection">
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="PlayerGuideObjOrnament" parentKey="Ornament"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Corner-Ornament">
|
||||
<Size><AbsDimension x="20" y="20"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<FontString name="PlayerGuideObjHeading" parentKey="Heading" inherits="MW_PG_Section" justifyH="LEFT" text="MW_PLAYER_GUIDE_OBJECTIVES">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="26" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="PlayerGuideObjCount" parentKey="Count" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="PlayerGuideObjHeading" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="6" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<Texture name="PlayerGuideObjDivider" parentKey="Divider"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Section-Divider">
|
||||
<Size><AbsDimension x="0" y="16"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-24"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-4" y="-24"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<ScrollFrame name="PlayerGuideObjScroll" parentKey="ScrollFrame" inherits="UIPanelScrollFrameTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-48"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-8" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<ScrollChild>
|
||||
<Frame parentKey="ScrollChild">
|
||||
<Size><AbsDimension x="600" y="200"/></Size>
|
||||
</Frame>
|
||||
</ScrollChild>
|
||||
</ScrollFrame>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 4) Подходящие подземелья — позиция задаётся Lua в OnLoad -->
|
||||
<Frame name="PlayerGuideRecsSection" parentKey="RecommendationsSection">
|
||||
<Size><AbsDimension x="0" y="150"/></Size>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="PlayerGuideRecsOrnament" parentKey="Ornament"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Corner-Ornament">
|
||||
<Size><AbsDimension x="20" y="20"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<FontString name="PlayerGuideRecsHeading" parentKey="Heading" inherits="MW_PG_Section" justifyH="LEFT" text="MW_PLAYER_GUIDE_RECS">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="26" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="PlayerGuideRecsCount" parentKey="Count" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeTo="PlayerGuideRecsHeading" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="6" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<Texture name="PlayerGuideRecsDivider" parentKey="Divider"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Section-Divider">
|
||||
<Size><AbsDimension x="0" y="16"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-24"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-4" y="-24"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<ScrollFrame name="PlayerGuideRecScroll" parentKey="ScrollFrame" inherits="UIPanelScrollFrameTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-46"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-36" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<ScrollChild>
|
||||
<Frame parentKey="ScrollChild">
|
||||
<Size><AbsDimension x="900" y="96"/></Size>
|
||||
</Frame>
|
||||
</ScrollChild>
|
||||
</ScrollFrame>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 5) Пустое состояние -->
|
||||
<Frame name="PlayerGuideEmptyState" parentKey="EmptyState" hidden="true">
|
||||
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="20"/></Offset></Anchor></Anchors>
|
||||
<Size><AbsDimension x="460" y="200"/></Size>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString name="PlayerGuideEmptyTitle" parentKey="Title" inherits="MW_PG_Title" justifyH="CENTER" text="MW_PLAYER_GUIDE_EMPTY_TITLE">
|
||||
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="-20"/></Offset></Anchor></Anchors>
|
||||
<Size><AbsDimension x="460" y="28"/></Size>
|
||||
</FontString>
|
||||
<FontString name="PlayerGuideEmptyBody" parentKey="Body" inherits="MW_PG_Body" justifyH="CENTER" text="MW_PLAYER_GUIDE_EMPTY_BODY">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="PlayerGuideEmptyTitle" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="0" y="-10"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Size><AbsDimension x="420" y="60"/></Size>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button name="PlayerGuideEmptyRefresh" parentKey="RefreshButton" inherits="UIPanelButtonTemplate2" text="MW_PLAYER_GUIDE_REFRESH">
|
||||
<Size><AbsDimension x="120" y="24"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="PlayerGuideEmptyBody" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="0" y="-14"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
local T = "Interface\\AddOns\\MoonWellClient\\EncounterJournal\\Textures\\PlayerGuide\\"
|
||||
self:SetNormalTexture(T.."Button-Up")
|
||||
self:SetPushedTexture(T.."Button-Down")
|
||||
self:SetHighlightTexture(T.."Button-Highlight")
|
||||
</OnLoad>
|
||||
<OnClick function="MW_PlayerGuide_RefreshButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 6) Загрузка -->
|
||||
<Frame name="PlayerGuideLoadingState" parentKey="LoadingState" hidden="true">
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
<Size><AbsDimension x="300" y="80"/></Size>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString name="PlayerGuideLoadingTitle" parentKey="Title" inherits="MW_PG_Section" justifyH="CENTER" text="MW_PLAYER_GUIDE_LOADING_TITLE">
|
||||
<Anchors><Anchor point="TOP"/></Anchors>
|
||||
<Size><AbsDimension x="300" y="20"/></Size>
|
||||
</FontString>
|
||||
<FontString name="PlayerGuideLoadingBody" parentKey="Body" inherits="MW_PG_Italic" justifyH="CENTER" text="MW_PLAYER_GUIDE_LOADING_BODY">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="PlayerGuideLoadingTitle" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="0" y="-6"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Size><AbsDimension x="300" y="16"/></Size>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
|
||||
</Frames>
|
||||
|
||||
<Scripts>
|
||||
<OnLoad function="MW_PlayerGuide_OnLoad"/>
|
||||
<OnShow function="MW_PlayerGuide_OnShow"/>
|
||||
<OnMouseWheel function="MW_PlayerGuide_OnMouseWheel"/>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
|
||||
</Ui>
|
||||
@@ -0,0 +1,920 @@
|
||||
-- Compatibility shims for the Sirus Encounter Journal backport on 3.3.5a.
|
||||
|
||||
if not tIndexOf then
|
||||
function tIndexOf(tbl, value)
|
||||
if not tbl then
|
||||
return nil
|
||||
end
|
||||
for index, entry in ipairs(tbl) do
|
||||
if entry == value then
|
||||
return index
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
if not CopyTable then
|
||||
function CopyTable(tbl)
|
||||
if type(tbl) ~= "table" then
|
||||
return tbl
|
||||
end
|
||||
local copy = {}
|
||||
for key, value in pairs(tbl) do
|
||||
copy[key] = CopyTable(value)
|
||||
end
|
||||
return copy
|
||||
end
|
||||
end
|
||||
|
||||
if not table.wipe then
|
||||
function table.wipe(tbl)
|
||||
for key in pairs(tbl) do
|
||||
tbl[key] = nil
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
end
|
||||
|
||||
if not tCompare then
|
||||
function tCompare(lhsTable, rhsTable, depth)
|
||||
depth = depth or 1
|
||||
for key, value in pairs(lhsTable) do
|
||||
if type(value) == "table" then
|
||||
local rhsValue = rhsTable[key]
|
||||
if type(rhsValue) ~= "table" then
|
||||
return false
|
||||
end
|
||||
if depth > 1 then
|
||||
if not tCompare(value, rhsValue, depth - 1) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
elseif value ~= rhsTable[key] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for key in pairs(rhsTable) do
|
||||
if lhsTable[key] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
tsort = tsort or table.sort
|
||||
|
||||
bit = bit or {}
|
||||
if not bit.band then
|
||||
function bit.band(a, b)
|
||||
local result = 0
|
||||
local bitValue = 1
|
||||
a = a or 0
|
||||
b = b or 0
|
||||
while a > 0 or b > 0 do
|
||||
local aBit = a % 2
|
||||
local bBit = b % 2
|
||||
if aBit == 1 and bBit == 1 then
|
||||
result = result + bitValue
|
||||
end
|
||||
a = math.floor(a / 2)
|
||||
b = math.floor(b / 2)
|
||||
bitValue = bitValue * 2
|
||||
end
|
||||
return result
|
||||
end
|
||||
end
|
||||
bitband = bitband or bit.band
|
||||
|
||||
if not string.trim then
|
||||
function string.trim(value)
|
||||
return value and value:match("^%s*(.-)%s*$") or value
|
||||
end
|
||||
end
|
||||
|
||||
utf8 = utf8 or {}
|
||||
utf8.find = utf8.find or string.find
|
||||
utf8.upper = utf8.upper or string.upper
|
||||
|
||||
LE_EXPANSION_CLASSIC = LE_EXPANSION_CLASSIC or 0
|
||||
LE_EXPANSION_BURNING_CRUSADE = LE_EXPANSION_BURNING_CRUSADE or 1
|
||||
LE_EXPANSION_WRATH_OF_THE_LICH_KING = LE_EXPANSION_WRATH_OF_THE_LICH_KING or 2
|
||||
LE_EXPANSION_CATACLYSM = LE_EXPANSION_CATACLYSM or 3
|
||||
LE_EXPANSION_MISTS_OF_PANDARIA = LE_EXPANSION_MISTS_OF_PANDARIA or 4
|
||||
LE_EXPANSION_WARLORDS_OF_DRAENOR = LE_EXPANSION_WARLORDS_OF_DRAENOR or 5
|
||||
LE_EXPANSION_LEGION = LE_EXPANSION_LEGION or 6
|
||||
LE_EXPANSION_BATTLE_FOR_AZEROTH = LE_EXPANSION_BATTLE_FOR_AZEROTH or 7
|
||||
LE_EXPANSION_SHADOWLANDS = LE_EXPANSION_SHADOWLANDS or 8
|
||||
LE_EXPANSION_DRAGONFLIGHT = LE_EXPANSION_DRAGONFLIGHT or 9
|
||||
LE_EXPANSION_WAR_WITHIN = LE_EXPANSION_WAR_WITHIN or 10
|
||||
LE_EXPANSION_MIDNIGHT = LE_EXPANSION_MIDNIGHT or 11
|
||||
LE_EXPANSION_LEVEL_CURRENT = LE_EXPANSION_LEVEL_CURRENT or LE_EXPANSION_WRATH_OF_THE_LICH_KING
|
||||
|
||||
MAX_PLAYER_LEVEL_TABLE = MAX_PLAYER_LEVEL_TABLE or {}
|
||||
MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_CLASSIC] = MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_CLASSIC] or 60
|
||||
MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_BURNING_CRUSADE] = MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_BURNING_CRUSADE] or 70
|
||||
MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_WRATH_OF_THE_LICH_KING] = MAX_PLAYER_LEVEL_TABLE[LE_EXPANSION_WRATH_OF_THE_LICH_KING] or 80
|
||||
|
||||
PLAYER_FACTION_GROUP = PLAYER_FACTION_GROUP or {}
|
||||
PLAYER_FACTION_GROUP.Alliance = PLAYER_FACTION_GROUP.Alliance or 1
|
||||
PLAYER_FACTION_GROUP.Horde = PLAYER_FACTION_GROUP.Horde or 2
|
||||
PLAYER_FACTION_GROUP.Neutral = PLAYER_FACTION_GROUP.Neutral or 3
|
||||
PLAYER_FACTION_GROUP.Renegade = PLAYER_FACTION_GROUP.Renegade or 4
|
||||
|
||||
QUESTION_MARK_ICON = QUESTION_MARK_ICON or "Interface\\Icons\\INV_Misc_QuestionMark"
|
||||
|
||||
CLASS_ID_WARRIOR = CLASS_ID_WARRIOR or 1
|
||||
CLASS_ID_PALADIN = CLASS_ID_PALADIN or 2
|
||||
CLASS_ID_HUNTER = CLASS_ID_HUNTER or 3
|
||||
CLASS_ID_ROGUE = CLASS_ID_ROGUE or 4
|
||||
CLASS_ID_PRIEST = CLASS_ID_PRIEST or 5
|
||||
CLASS_ID_DEATHKNIGHT = CLASS_ID_DEATHKNIGHT or 6
|
||||
CLASS_ID_SHAMAN = CLASS_ID_SHAMAN or 7
|
||||
CLASS_ID_MAGE = CLASS_ID_MAGE or 8
|
||||
CLASS_ID_WARLOCK = CLASS_ID_WARLOCK or 9
|
||||
CLASS_ID_DRUID = CLASS_ID_DRUID or 11
|
||||
|
||||
if not SetParentFrameLevel then
|
||||
function SetParentFrameLevel(frame, offset)
|
||||
frame:SetFrameLevel(frame:GetParent():GetFrameLevel() + (offset or 0))
|
||||
end
|
||||
end
|
||||
|
||||
if not IsOnGlueScreen then
|
||||
function IsOnGlueScreen() return false end
|
||||
end
|
||||
|
||||
if not RegisterCustomEvent then
|
||||
function RegisterCustomEvent() end
|
||||
end
|
||||
|
||||
if not UnregisterCustomEvent then
|
||||
function UnregisterCustomEvent() end
|
||||
end
|
||||
|
||||
C_Timer = C_Timer or {}
|
||||
if not C_Timer.NewTimer then
|
||||
local timerFrame
|
||||
local timers = {}
|
||||
|
||||
local function EnsureTimerFrame()
|
||||
if timerFrame or not CreateFrame then
|
||||
return
|
||||
end
|
||||
|
||||
timerFrame = CreateFrame("Frame")
|
||||
timerFrame:SetScript("OnUpdate", function(_, elapsed)
|
||||
for timer in pairs(timers) do
|
||||
timer.remaining = timer.remaining - elapsed
|
||||
if timer.remaining <= 0 then
|
||||
timers[timer] = nil
|
||||
if not timer.cancelled then
|
||||
timer.callback()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function C_Timer.NewTimer(seconds, callback)
|
||||
local timer = {
|
||||
remaining = seconds or 0,
|
||||
callback = callback or function() end,
|
||||
}
|
||||
|
||||
function timer:Cancel()
|
||||
self.cancelled = true
|
||||
timers[self] = nil
|
||||
end
|
||||
|
||||
EnsureTimerFrame()
|
||||
timers[timer] = true
|
||||
return timer
|
||||
end
|
||||
end
|
||||
C_Timer.After = C_Timer.After or function(seconds, callback)
|
||||
return C_Timer.NewTimer(seconds, callback)
|
||||
end
|
||||
|
||||
if not IsInterfaceDevClient then
|
||||
function IsInterfaceDevClient()
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if not IsGMAccount then
|
||||
function IsGMAccount()
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if not FireCustomClientEvent then
|
||||
function FireCustomClientEvent(event, ...)
|
||||
if EventRegistry and EventRegistry.TriggerEvent then
|
||||
EventRegistry:TriggerEvent(event, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
EventRegistry = EventRegistry or {}
|
||||
EventRegistry.registry = EventRegistry.registry or {}
|
||||
EventRegistry.TriggerEvent = EventRegistry.TriggerEvent or function(self, event, ...)
|
||||
local callbacks = self.registry[event]
|
||||
if callbacks then
|
||||
for _, callback in ipairs(callbacks) do
|
||||
callback(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
EventRegistry.RegisterCallback = EventRegistry.RegisterCallback or function(self, event, callback)
|
||||
self.registry[event] = self.registry[event] or {}
|
||||
table.insert(self.registry[event], callback)
|
||||
end
|
||||
|
||||
C_Service = C_Service or {}
|
||||
C_Service.GetRealmID = function() return 0 end
|
||||
C_Service.GetRealmStage = C_Service.GetRealmStage or function() return 0 end
|
||||
C_Service.GetCustomValue = C_Service.GetCustomValue or function() return 0 end
|
||||
C_Service.IsInGMMode = C_Service.IsInGMMode or IsGMAccount
|
||||
C_Service.IsGMAccount = C_Service.IsGMAccount or IsGMAccount
|
||||
C_Service.IsRenegadeRealm = C_Service.IsRenegadeRealm or function() return false end
|
||||
C_Service.IsHardcoreEnabledOnRealm = C_Service.IsHardcoreEnabledOnRealm or function() return false end
|
||||
|
||||
C_MythicPlus = C_MythicPlus or {}
|
||||
C_MythicPlus.IsMythicPlusActive = C_MythicPlus.IsMythicPlusActive or function() return false end
|
||||
|
||||
C_PvP = C_PvP or {}
|
||||
|
||||
C_LFGList = C_LFGList or {}
|
||||
C_LFGList.IsPremadeGroupFinderEnabled = C_LFGList.IsPremadeGroupFinderEnabled or function() return false end
|
||||
|
||||
if not MicroButtonPulseStop then
|
||||
function MicroButtonPulseStop() end
|
||||
end
|
||||
|
||||
if not EJSuggestFrame_OpenFrame then
|
||||
function EJSuggestFrame_OpenFrame() end
|
||||
end
|
||||
|
||||
if not EJSuggestTab_GetPlayerTierIndex then
|
||||
function EJSuggestTab_GetPlayerTierIndex()
|
||||
if EJ_GetCurrentTier then
|
||||
return EJ_GetCurrentTier()
|
||||
end
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
if not ChatEdit_TryInsertChatLink then
|
||||
function ChatEdit_TryInsertChatLink()
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if not SharedTooltip_SetBackdropStyle then
|
||||
function SharedTooltip_SetBackdropStyle(tooltip)
|
||||
if not tooltip then
|
||||
return
|
||||
end
|
||||
if tooltip.SetBackdropBorderColor and TOOLTIP_DEFAULT_COLOR then
|
||||
tooltip:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
|
||||
end
|
||||
if tooltip.SetBackdropColor and TOOLTIP_DEFAULT_BACKGROUND_COLOR then
|
||||
tooltip:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not HelpPlate_Hide then
|
||||
function HelpPlate_Hide() end
|
||||
end
|
||||
|
||||
if not HelpPlate_Show then
|
||||
function HelpPlate_Show() end
|
||||
end
|
||||
|
||||
-- 3.3.5 has no GetNumClasses/GetClassInfo (added in Cataclysm). Sirus EJ uses
|
||||
-- them to populate the loot class filter dropdown.
|
||||
local MOONWELL_CLASS_INFO = {
|
||||
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.WARRIOR or "Warrior", "WARRIOR", CLASS_ID_WARRIOR},
|
||||
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.PALADIN or "Paladin", "PALADIN", CLASS_ID_PALADIN},
|
||||
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.HUNTER or "Hunter", "HUNTER", CLASS_ID_HUNTER},
|
||||
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.ROGUE or "Rogue", "ROGUE", CLASS_ID_ROGUE},
|
||||
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.PRIEST or "Priest", "PRIEST", CLASS_ID_PRIEST},
|
||||
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.DEATHKNIGHT or "Death Knight", "DEATHKNIGHT", CLASS_ID_DEATHKNIGHT},
|
||||
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.SHAMAN or "Shaman", "SHAMAN", CLASS_ID_SHAMAN},
|
||||
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.MAGE or "Mage", "MAGE", CLASS_ID_MAGE},
|
||||
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.WARLOCK or "Warlock", "WARLOCK", CLASS_ID_WARLOCK},
|
||||
{LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE.DRUID or "Druid", "DRUID", CLASS_ID_DRUID},
|
||||
}
|
||||
|
||||
if not GetNumClasses then
|
||||
function GetNumClasses()
|
||||
return #MOONWELL_CLASS_INFO
|
||||
end
|
||||
end
|
||||
|
||||
if not GetClassInfo then
|
||||
function GetClassInfo(index)
|
||||
local info = MOONWELL_CLASS_INFO[index]
|
||||
if info then
|
||||
return info[1], info[2], info[3]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
CLASS_ID_DEMONHUNTER = CLASS_ID_DEMONHUNTER or 12
|
||||
local function EnsureColorMethods(color)
|
||||
if type(color) ~= "table" then
|
||||
return color
|
||||
end
|
||||
|
||||
color.GetRGB = color.GetRGB or function(self)
|
||||
return self.r or 1, self.g or 1, self.b or 1
|
||||
end
|
||||
color.GetRGBA = color.GetRGBA or function(self)
|
||||
return self.r or 1, self.g or 1, self.b or 1, self.a or 1
|
||||
end
|
||||
color.WrapTextInColorCode = color.WrapTextInColorCode or function(self, text)
|
||||
if self.colorStr then
|
||||
return "|c" .. self.colorStr .. text .. "|r"
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
||||
return color
|
||||
end
|
||||
|
||||
EnsureColorMethods(NORMAL_FONT_COLOR)
|
||||
EnsureColorMethods(HIGHLIGHT_FONT_COLOR)
|
||||
EnsureColorMethods(RED_FONT_COLOR)
|
||||
EnsureColorMethods(GREEN_FONT_COLOR)
|
||||
EnsureColorMethods(DISABLED_FONT_COLOR)
|
||||
LOOTJOURNAL_SOURCE_TOOLTIP_HEAD = LOOTJOURNAL_SOURCE_TOOLTIP_HEAD or "Способ получения:"
|
||||
RETURN_TO_DEFAULT = RETURN_TO_DEFAULT or "Настройки по умолчанию"
|
||||
GUIDEBOOK = GUIDEBOOK or "Путеводитель"
|
||||
INSTANCES = INSTANCES or "Подземелья"
|
||||
RAIDS = RAIDS or "Рейды"
|
||||
LOOT_JOURNAL_ITEM_SETS = LOOT_JOURNAL_ITEM_SETS or "Комплекты"
|
||||
|
||||
-- Format string used by EncounterJournal_SetLootButton to label the loot
|
||||
-- source ("Босс: <name>"). Sirus ships it via locale globals; default to a
|
||||
-- compact form on 3.3.5.
|
||||
BOSS_INFO_STRING = BOSS_INFO_STRING or "Босс: %s"
|
||||
|
||||
-- Sirus ships a PKBT model mixin in SharedXML/SharedUIPanelPKBTTemplates;
|
||||
-- we don't carry it. The EJ DressUpModel OnShow does `self:OnShow()` and
|
||||
-- OnEvent does `self:OnEvent(...)`, expecting the mixin to provide them.
|
||||
PKBT_ModelMixin = PKBT_ModelMixin or {}
|
||||
PKBT_ModelMixin.OnShow = PKBT_ModelMixin.OnShow or function() end
|
||||
PKBT_ModelMixin.OnEvent = PKBT_ModelMixin.OnEvent or function() end
|
||||
PKBT_ModelMixin.OnLoad = PKBT_ModelMixin.OnLoad or function() end
|
||||
|
||||
if not SharedXML_Model_OnLoad then
|
||||
function SharedXML_Model_OnLoad() end
|
||||
end
|
||||
if not SharedXML_Model_OnEvent then
|
||||
function SharedXML_Model_OnEvent() end
|
||||
end
|
||||
|
||||
ITEM_CLASS_2 = ITEM_CLASS_2 or 2
|
||||
ITEM_CLASS_4 = ITEM_CLASS_4 or 4
|
||||
|
||||
for _, classID in ipairs({0, 2, 4, 5, 7, 9, 11, 12, 13, 14, 15}) do
|
||||
for subclassID = 0, 20 do
|
||||
local key = string.format("ITEM_SUB_CLASS_%d_%d", classID, subclassID)
|
||||
if _G[key] == nil then
|
||||
_G[key] = subclassID
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
LE_ITEM_FILTER_TYPE_HEAD = LE_ITEM_FILTER_TYPE_HEAD or 1
|
||||
LE_ITEM_FILTER_TYPE_NECK = LE_ITEM_FILTER_TYPE_NECK or 2
|
||||
LE_ITEM_FILTER_TYPE_SHOULDER = LE_ITEM_FILTER_TYPE_SHOULDER or 3
|
||||
LE_ITEM_FILTER_TYPE_CLOAK = LE_ITEM_FILTER_TYPE_CLOAK or 16
|
||||
LE_ITEM_FILTER_TYPE_CHEST = LE_ITEM_FILTER_TYPE_CHEST or 5
|
||||
LE_ITEM_FILTER_TYPE_WRIST = LE_ITEM_FILTER_TYPE_WRIST or 9
|
||||
LE_ITEM_FILTER_TYPE_HAND = LE_ITEM_FILTER_TYPE_HAND or 10
|
||||
LE_ITEM_FILTER_TYPE_WAIST = LE_ITEM_FILTER_TYPE_WAIST or 6
|
||||
LE_ITEM_FILTER_TYPE_LEGS = LE_ITEM_FILTER_TYPE_LEGS or 7
|
||||
LE_ITEM_FILTER_TYPE_FEET = LE_ITEM_FILTER_TYPE_FEET or 8
|
||||
LE_ITEM_FILTER_TYPE_MAIN_HAND = LE_ITEM_FILTER_TYPE_MAIN_HAND or 21
|
||||
LE_ITEM_FILTER_TYPE_OFF_HAND = LE_ITEM_FILTER_TYPE_OFF_HAND or 22
|
||||
LE_ITEM_FILTER_TYPE_2HWEAPON = LE_ITEM_FILTER_TYPE_2HWEAPON or 17
|
||||
LE_ITEM_FILTER_TYPE_HWEAPON = LE_ITEM_FILTER_TYPE_HWEAPON or 13
|
||||
LE_ITEM_FILTER_TYPE_RANGED = LE_ITEM_FILTER_TYPE_RANGED or 15
|
||||
LE_ITEM_FILTER_TYPE_FINGER = LE_ITEM_FILTER_TYPE_FINGER or 11
|
||||
LE_ITEM_FILTER_TYPE_TRINKET = LE_ITEM_FILTER_TYPE_TRINKET or 12
|
||||
|
||||
LE_ITEM_QUALITY_COMMON = LE_ITEM_QUALITY_COMMON or 1
|
||||
|
||||
BAG_ITEM_QUALITY_COLORS = BAG_ITEM_QUALITY_COLORS or {}
|
||||
local itemQualityColors = ITEM_QUALITY_COLORS or {}
|
||||
for quality = 0, 7 do
|
||||
local color = BAG_ITEM_QUALITY_COLORS[quality] or itemQualityColors[quality] or NORMAL_FONT_COLOR
|
||||
BAG_ITEM_QUALITY_COLORS[quality] = EnsureColorMethods(color or {})
|
||||
end
|
||||
|
||||
if not SetItemButtonQuality then
|
||||
function SetItemButtonQuality(button, quality)
|
||||
if not button or not button.IconBorder then
|
||||
return
|
||||
end
|
||||
|
||||
local color = quality and BAG_ITEM_QUALITY_COLORS[quality]
|
||||
if color then
|
||||
button.IconBorder:Show()
|
||||
button.IconBorder:SetVertexColor(color.r or 1, color.g or 1, color.b or 1)
|
||||
else
|
||||
button.IconBorder:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Enum = Enum or {}
|
||||
Enum.ItemQuality = Enum.ItemQuality or {
|
||||
Poor = 0,
|
||||
Common = 1,
|
||||
Uncommon = 2,
|
||||
Rare = 3,
|
||||
Epic = 4,
|
||||
Legendary = 5,
|
||||
Artifact = 6,
|
||||
Heirloom = 7,
|
||||
}
|
||||
Enum.ModelType = Enum.ModelType or {
|
||||
M2 = 0,
|
||||
Unit = 1,
|
||||
Creature = 2,
|
||||
Item = 3,
|
||||
ItemSet = 4,
|
||||
Illusion = 5,
|
||||
ItemTransmog = 6,
|
||||
Customization = 7,
|
||||
}
|
||||
Enum.HardcoreDeathReason = Enum.HardcoreDeathReason or {
|
||||
FAILED_DEATH = 1,
|
||||
RESTORE = 2,
|
||||
}
|
||||
local ITEM_CACHE_FIELD = {
|
||||
NAME_ENGB = 1,
|
||||
NAME_RURU = 2,
|
||||
RARITY = 3,
|
||||
ILEVEL = 4,
|
||||
MINLEVEL = 5,
|
||||
TYPE = 6,
|
||||
SUBTYPE = 7,
|
||||
STACKCOUNT = 8,
|
||||
EQUIPLOC = 9,
|
||||
TEXTURE = 10,
|
||||
VENDORPRICE = 11,
|
||||
VENDORPRIC = 11,
|
||||
}
|
||||
local ITEM_ID_FIELD = 0
|
||||
local ITEM_LINK_FIELD = -1
|
||||
|
||||
Enum.ItemCacheField = Enum.ItemCacheField or {}
|
||||
for key, value in pairs(ITEM_CACHE_FIELD) do
|
||||
Enum.ItemCacheField[key] = value
|
||||
end
|
||||
Enum.ItemCacheField.ITEM_ID = ITEM_ID_FIELD
|
||||
|
||||
SHARED_INVTYPE_BY_ID = SHARED_INVTYPE_BY_ID or {
|
||||
[0] = "",
|
||||
[1] = "INVTYPE_HEAD",
|
||||
[2] = "INVTYPE_NECK",
|
||||
[3] = "INVTYPE_SHOULDER",
|
||||
[4] = "INVTYPE_BODY",
|
||||
[5] = "INVTYPE_CHEST",
|
||||
[6] = "INVTYPE_WAIST",
|
||||
[7] = "INVTYPE_LEGS",
|
||||
[8] = "INVTYPE_FEET",
|
||||
[9] = "INVTYPE_WRIST",
|
||||
[10] = "INVTYPE_HAND",
|
||||
[11] = "INVTYPE_FINGER",
|
||||
[12] = "INVTYPE_TRINKET",
|
||||
[13] = "INVTYPE_WEAPON",
|
||||
[14] = "INVTYPE_SHIELD",
|
||||
[15] = "INVTYPE_RANGED",
|
||||
[16] = "INVTYPE_CLOAK",
|
||||
[17] = "INVTYPE_2HWEAPON",
|
||||
[18] = "INVTYPE_BAG",
|
||||
[19] = "INVTYPE_TABARD",
|
||||
[20] = "INVTYPE_ROBE",
|
||||
[21] = "INVTYPE_WEAPONMAINHAND",
|
||||
[22] = "INVTYPE_WEAPONOFFHAND",
|
||||
[23] = "INVTYPE_HOLDABLE",
|
||||
[24] = "INVTYPE_AMMO",
|
||||
[25] = "INVTYPE_THROWN",
|
||||
[26] = "INVTYPE_RANGEDRIGHT",
|
||||
[27] = "INVTYPE_QUIVER",
|
||||
[28] = "INVTYPE_RELIC",
|
||||
}
|
||||
|
||||
C_FactionManager = C_FactionManager or {}
|
||||
C_FactionManager.GetFactionInfoOriginal = C_FactionManager.GetFactionInfoOriginal or function()
|
||||
local faction = UnitFactionGroup and UnitFactionGroup("player")
|
||||
return PLAYER_FACTION_GROUP[faction] or PLAYER_FACTION_GROUP.Alliance
|
||||
end
|
||||
C_FactionManager.RegisterCallback = C_FactionManager.RegisterCallback or function(callback)
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
end
|
||||
|
||||
C_Item = C_Item or {}
|
||||
|
||||
local ExistingCItemGetItemInfo = C_Item.GetItemInfo
|
||||
local ExistingCItemRequestServerCache = C_Item.RequestServerCache
|
||||
local ExistingCItemGetItemIDFromString = C_Item.GetItemIDFromString
|
||||
local ITEM_CACHE_CALLBACKS = {}
|
||||
local ITEM_QUALITY_HEX = {
|
||||
[0] = "ff9d9d9d",
|
||||
[1] = "ffffffff",
|
||||
[2] = "ff1eff00",
|
||||
[3] = "ff0070dd",
|
||||
[4] = "ffa335ee",
|
||||
[5] = "ffff8000",
|
||||
[6] = "ffe6cc80",
|
||||
[7] = "ffe6cc80",
|
||||
}
|
||||
local ITEMS_CACHE_INITIALIZED
|
||||
|
||||
local function GetItemIDFromAny(item)
|
||||
if type(item) == "number" then
|
||||
return item
|
||||
end
|
||||
if type(item) == "string" then
|
||||
local itemsCache = _G.ItemsCache
|
||||
if type(itemsCache) == "table" and type(itemsCache[item]) == "table" then
|
||||
return itemsCache[item][ITEM_ID_FIELD]
|
||||
end
|
||||
return tonumber(item:match("item:(%d+)") or item:match("^(%d+)$"))
|
||||
end
|
||||
end
|
||||
|
||||
local function CreateItemCacheLink(itemName, itemID, itemRarity, itemMinLevel)
|
||||
return string.format("|c%s|Hitem:%d:0:0:0:0:0:0:0:%d|h[%s]|h|r",
|
||||
ITEM_QUALITY_HEX[itemRarity] or "ffffffff",
|
||||
itemID or 0,
|
||||
itemMinLevel or 0,
|
||||
itemName or UNKNOWN or ""
|
||||
)
|
||||
end
|
||||
|
||||
local function EnsureItemsCache()
|
||||
if ITEMS_CACHE_INITIALIZED then
|
||||
return _G.ItemsCache
|
||||
end
|
||||
|
||||
local itemsCache = type(_G.ItemsCache) == "table" and _G.ItemsCache or nil
|
||||
local fileIndex = 1
|
||||
local itemCacheTable = _G["ItemsCache" .. fileIndex]
|
||||
|
||||
while type(itemCacheTable) == "table" do
|
||||
if not itemsCache then
|
||||
itemsCache = itemCacheTable
|
||||
_G.ItemsCache = itemsCache
|
||||
elseif itemCacheTable ~= itemsCache then
|
||||
for itemID, itemData in pairs(itemCacheTable) do
|
||||
itemsCache[itemID] = itemData
|
||||
end
|
||||
table.wipe(itemCacheTable)
|
||||
end
|
||||
|
||||
_G["ItemsCache" .. fileIndex] = nil
|
||||
fileIndex = fileIndex + 1
|
||||
itemCacheTable = _G["ItemsCache" .. fileIndex]
|
||||
end
|
||||
|
||||
if itemsCache then
|
||||
local localeIndex = GetLocale and GetLocale() == "ruRU" and ITEM_CACHE_FIELD.NAME_RURU or ITEM_CACHE_FIELD.NAME_ENGB
|
||||
local namedItems = {}
|
||||
|
||||
for itemID, itemData in pairs(itemsCache) do
|
||||
if type(itemID) == "number" and type(itemData) == "table" then
|
||||
itemData[ITEM_ID_FIELD] = itemID
|
||||
itemData[ITEM_LINK_FIELD] = nil
|
||||
|
||||
local itemName = itemData[localeIndex]
|
||||
if itemName and itemName ~= "" then
|
||||
namedItems[itemName] = itemData
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(itemsCache, {__index = namedItems})
|
||||
end
|
||||
|
||||
ITEMS_CACHE_INITIALIZED = true
|
||||
return itemsCache
|
||||
end
|
||||
|
||||
function C_Item.GetItemInfoCache(item)
|
||||
local itemsCache = EnsureItemsCache()
|
||||
if not itemsCache then
|
||||
return
|
||||
end
|
||||
|
||||
local itemID = GetItemIDFromAny(item)
|
||||
if not itemID then
|
||||
return
|
||||
end
|
||||
|
||||
local cacheData = itemsCache[itemID]
|
||||
if type(cacheData) ~= "table" then
|
||||
return
|
||||
end
|
||||
|
||||
local localeIndex = GetLocale and GetLocale() == "ruRU" and ITEM_CACHE_FIELD.NAME_RURU or ITEM_CACHE_FIELD.NAME_ENGB
|
||||
local itemName = cacheData[localeIndex] or cacheData[ITEM_CACHE_FIELD.NAME_ENGB]
|
||||
if not itemName or itemName == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local itemRarity = cacheData[ITEM_CACHE_FIELD.RARITY] or LE_ITEM_QUALITY_COMMON
|
||||
local itemMinLevel = cacheData[ITEM_CACHE_FIELD.MINLEVEL] or 0
|
||||
local classID = cacheData[ITEM_CACHE_FIELD.TYPE]
|
||||
local subclassID = cacheData[ITEM_CACHE_FIELD.SUBTYPE]
|
||||
local equipLocID = cacheData[ITEM_CACHE_FIELD.EQUIPLOC] or 0
|
||||
|
||||
if not cacheData[ITEM_LINK_FIELD] then
|
||||
cacheData[ITEM_LINK_FIELD] = CreateItemCacheLink(itemName, itemID, itemRarity, itemMinLevel)
|
||||
end
|
||||
|
||||
return itemName,
|
||||
cacheData[ITEM_LINK_FIELD],
|
||||
itemRarity,
|
||||
cacheData[ITEM_CACHE_FIELD.ILEVEL],
|
||||
itemMinLevel,
|
||||
_G["ITEM_CLASS_" .. tostring(classID)] or classID,
|
||||
_G["ITEM_SUB_CLASS_" .. tostring(classID) .. "_" .. tostring(subclassID)] or subclassID,
|
||||
cacheData[ITEM_CACHE_FIELD.STACKCOUNT],
|
||||
SHARED_INVTYPE_BY_ID[equipLocID],
|
||||
"Interface\\Icons\\" .. tostring(cacheData[ITEM_CACHE_FIELD.TEXTURE] or "INV_Misc_QuestionMark"),
|
||||
cacheData[ITEM_CACHE_FIELD.VENDORPRICE],
|
||||
itemID,
|
||||
classID,
|
||||
subclassID,
|
||||
equipLocID
|
||||
end
|
||||
|
||||
local function GetItemInfoCompat(item)
|
||||
local name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = GetItemInfo(item)
|
||||
if name then
|
||||
return name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice
|
||||
end
|
||||
|
||||
return C_Item.GetItemInfoCache(item)
|
||||
end
|
||||
|
||||
local function FireItemCacheCallbacks(itemID)
|
||||
local callbacks = ITEM_CACHE_CALLBACKS[itemID]
|
||||
if not callbacks then
|
||||
return
|
||||
end
|
||||
|
||||
local name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = GetItemInfoCompat(itemID)
|
||||
if not name then
|
||||
return
|
||||
end
|
||||
|
||||
ITEM_CACHE_CALLBACKS[itemID] = nil
|
||||
for _, callback in ipairs(callbacks) do
|
||||
callback(itemID, name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice)
|
||||
end
|
||||
end
|
||||
|
||||
local function QueueItemCacheCallback(item, callback)
|
||||
if not callback then
|
||||
return
|
||||
end
|
||||
|
||||
local itemID = tonumber(item)
|
||||
if not itemID and type(item) == "string" then
|
||||
itemID = tonumber(item:match("item:(%d+)") or item:match("^(%d+)$"))
|
||||
end
|
||||
if not itemID then
|
||||
return
|
||||
end
|
||||
|
||||
ITEM_CACHE_CALLBACKS[itemID] = ITEM_CACHE_CALLBACKS[itemID] or {}
|
||||
table.insert(ITEM_CACHE_CALLBACKS[itemID], callback)
|
||||
end
|
||||
|
||||
local itemCacheFrame
|
||||
local function EnsureItemCacheFrame()
|
||||
if itemCacheFrame or not CreateFrame then
|
||||
return
|
||||
end
|
||||
|
||||
itemCacheFrame = CreateFrame("Frame")
|
||||
itemCacheFrame:RegisterEvent("GET_ITEM_INFO_RECEIVED")
|
||||
itemCacheFrame:SetScript("OnEvent", function(_, _, itemID)
|
||||
if itemID then
|
||||
FireItemCacheCallbacks(itemID)
|
||||
else
|
||||
for queuedItemID in pairs(ITEM_CACHE_CALLBACKS) do
|
||||
FireItemCacheCallbacks(queuedItemID)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
C_Item.GetItemInfo = function(item, skipClientCache, callback, ...)
|
||||
local name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice
|
||||
if ExistingCItemGetItemInfo then
|
||||
name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = ExistingCItemGetItemInfo(item, skipClientCache, nil, ...)
|
||||
end
|
||||
if not name then
|
||||
name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = GetItemInfoCompat(item)
|
||||
end
|
||||
|
||||
if callback then
|
||||
if name then
|
||||
callback(GetItemIDFromAny(item) or item, name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice)
|
||||
else
|
||||
QueueItemCacheCallback(item, callback)
|
||||
EnsureItemCacheFrame()
|
||||
end
|
||||
end
|
||||
return name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice
|
||||
end
|
||||
|
||||
C_Item.RequestServerCache = function(item, callback)
|
||||
local name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = GetItemInfoCompat(item)
|
||||
if callback then
|
||||
if name then
|
||||
callback(GetItemIDFromAny(item) or item, name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice)
|
||||
elseif ExistingCItemRequestServerCache then
|
||||
return ExistingCItemRequestServerCache(item, callback)
|
||||
else
|
||||
QueueItemCacheCallback(item, callback)
|
||||
EnsureItemCacheFrame()
|
||||
end
|
||||
end
|
||||
return name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice
|
||||
end
|
||||
C_Item.GetItemIDFromString = function(item)
|
||||
local itemID = ExistingCItemGetItemIDFromString and ExistingCItemGetItemIDFromString(item)
|
||||
if itemID then
|
||||
return itemID
|
||||
end
|
||||
|
||||
EnsureItemsCache()
|
||||
return GetItemIDFromAny(item)
|
||||
end
|
||||
|
||||
EnsureItemsCache()
|
||||
|
||||
if not Mixin then
|
||||
function Mixin(object, ...)
|
||||
for i = 1, select("#", ...) do
|
||||
local mixin = select(i, ...)
|
||||
for key, value in pairs(mixin) do
|
||||
object[key] = value
|
||||
end
|
||||
end
|
||||
return object
|
||||
end
|
||||
end
|
||||
|
||||
if not CreateFromMixins then
|
||||
function CreateFromMixins(...)
|
||||
return Mixin({}, ...)
|
||||
end
|
||||
end
|
||||
|
||||
if not CreateAndInitFromMixin then
|
||||
function CreateAndInitFromMixin(mixin, ...)
|
||||
local object = CreateFromMixins(mixin)
|
||||
object:Init(...)
|
||||
return object
|
||||
end
|
||||
end
|
||||
|
||||
if not InlineHyperlinkFrame_SimpleHTMLAsFontString_OnLoad then
|
||||
function InlineHyperlinkFrame_SimpleHTMLAsFontString_OnLoad(self)
|
||||
if PKBT_SimpleHTMLAsFontStringMixin then
|
||||
Mixin(self, PKBT_SimpleHTMLAsFontStringMixin)
|
||||
if self.OnLoadSimpleHTMLAsFontString then
|
||||
self:OnLoadSimpleHTMLAsFontString()
|
||||
end
|
||||
elseif self.GetRegions and not self:GetRegions() then
|
||||
local meta = getmetatable(self)
|
||||
local methods = meta and meta.__index
|
||||
if methods and methods.SetText then
|
||||
methods.SetText(self, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function InstallTextureAtlasShim()
|
||||
if not CreateFrame then
|
||||
return
|
||||
end
|
||||
|
||||
local texture = CreateFrame("Frame"):CreateTexture(nil, "ARTWORK")
|
||||
local meta = getmetatable(texture)
|
||||
local methods = meta and meta.__index
|
||||
|
||||
local atlasToTexture = {
|
||||
["adventureguide-pane-large"] = {335, 337, 0.001953, 0.656250, 0.320312, 0.978516, "Interface\\EncounterJournal\\AdventureGuide"},
|
||||
["adventureguide-pane-small"] = {344, 161, 0.001953, 0.673828, 0.001953, 0.316406, "Interface\\EncounterJournal\\AdventureGuide"},
|
||||
["adventureguide-ring"] = {94, 95, 0.775391, 0.958984, 0.001953, 0.187500, "Interface\\EncounterJournal\\AdventureGuide"},
|
||||
["adventureguide-rewardring"] = {48, 48, 0.677734, 0.771484, 0.001953, 0.095703, "Interface\\EncounterJournal\\AdventureGuide"},
|
||||
["adventureguide-redx"] = {35, 34, 0.677734, 0.746094, 0.175781, 0.242188, "Interface\\EncounterJournal\\AdventureGuide"},
|
||||
|
||||
["UI-EJ-Classic"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds1"},
|
||||
["UI-EJ-BurningCrusade"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds1"},
|
||||
["UI-EJ-WrathoftheLichKing"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds2"},
|
||||
["UI-EJ-Cataclysm"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds2"},
|
||||
["UI-EJ-MistsofPandaria"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds3"},
|
||||
["UI-EJ-WarlordsofDraenor"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds3"},
|
||||
["UI-EJ-BattleforAzeroth"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds4"},
|
||||
["UI-EJ-Legion"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds4"},
|
||||
["UI-EJ-Dragonflight"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds5"},
|
||||
["UI-EJ-Shadowlands"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds5"},
|
||||
["UI-EJ-TheWarWithin"] = {786, 425, 0.000977, 0.768555, 0.000977, 0.416016, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds6"},
|
||||
["UI-EJ-Midnight"] = {786, 425, 0.000977, 0.768555, 0.417969, 0.833008, "Interface\\EncounterJournal\\DungeonJournalTierBackgrounds6"},
|
||||
}
|
||||
|
||||
if type(methods) == "table" and not methods.SetAtlas then
|
||||
methods.SetAtlas = function(self, atlas, useAtlasSize)
|
||||
local atlasInfo = atlasToTexture[atlas]
|
||||
if atlasInfo then
|
||||
self:SetTexture(atlasInfo[7])
|
||||
self:SetTexCoord(atlasInfo[3], atlasInfo[4], atlasInfo[5], atlasInfo[6])
|
||||
if useAtlasSize then
|
||||
self:SetSize(atlasInfo[1], atlasInfo[2])
|
||||
end
|
||||
elseif SetAtlasTexture then
|
||||
local ok = pcall(SetAtlasTexture, self, atlas)
|
||||
if not ok then
|
||||
self:SetTexture(nil)
|
||||
end
|
||||
else
|
||||
self:SetTexture(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
InstallTextureAtlasShim()
|
||||
|
||||
local function InstallFrameMethodShims()
|
||||
if not CreateFrame then
|
||||
return
|
||||
end
|
||||
|
||||
local frame = CreateFrame("Frame")
|
||||
local meta = getmetatable(frame)
|
||||
local methods = meta and meta.__index
|
||||
if type(methods) ~= "table" or methods.SetParentArray then
|
||||
return
|
||||
end
|
||||
|
||||
methods.SetParentArray = function(self, arrayName)
|
||||
local parent = self:GetParent()
|
||||
if not parent or not arrayName then
|
||||
return
|
||||
end
|
||||
|
||||
parent[arrayName] = parent[arrayName] or {}
|
||||
table.insert(parent[arrayName], self)
|
||||
end
|
||||
end
|
||||
|
||||
InstallFrameMethodShims()
|
||||
|
||||
if not EncounterJournal_SetSearchPreviewSelection then
|
||||
function EncounterJournal_SetSearchPreviewSelection(selectedIndex)
|
||||
if not EncounterJournal or not EncounterJournal.searchBox then
|
||||
return
|
||||
end
|
||||
|
||||
local searchBox = EncounterJournal.searchBox
|
||||
searchBox.selectedIndex = selectedIndex or 1
|
||||
|
||||
if searchBox.searchPreview then
|
||||
for index, button in ipairs(searchBox.searchPreview) do
|
||||
if button.selectedTexture then
|
||||
if index == searchBox.selectedIndex then
|
||||
button.selectedTexture:Show()
|
||||
else
|
||||
button.selectedTexture:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if searchBox.showAllResults and searchBox.showAllResults.selectedTexture then
|
||||
searchBox.showAllResults.selectedTexture:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,79 @@
|
||||
-- Optional AIO bridge: lets the server push fresh Encounter Journal data at runtime.
|
||||
-- The static fallback is loaded from Generated_EncounterJournal.lua, so the journal works
|
||||
-- without ever calling ApplyEncounterJournalData.
|
||||
|
||||
local function ApplyEncounterJournalData(data)
|
||||
if type(data) ~= "table" then
|
||||
return
|
||||
end
|
||||
|
||||
local function NormalizeTierInstances(tiers, tierInstances)
|
||||
if type(tiers) ~= "table" or type(tierInstances) ~= "table" then
|
||||
return tierInstances
|
||||
end
|
||||
|
||||
local tierIDs = {}
|
||||
for _, tierInfo in ipairs(tiers) do
|
||||
if type(tierInfo) == "table" then
|
||||
tierIDs[tierInfo[1]] = true
|
||||
end
|
||||
end
|
||||
|
||||
local hasTierKey = false
|
||||
for tierID in pairs(tierIDs) do
|
||||
if tierInstances[tierID] then
|
||||
hasTierKey = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not hasTierKey then
|
||||
return tierInstances
|
||||
end
|
||||
|
||||
local normalized = {}
|
||||
for tierID, instanceList in pairs(tierInstances) do
|
||||
if tierIDs[tierID] and type(instanceList) == "table" then
|
||||
for _, instanceID in ipairs(instanceList) do
|
||||
normalized[instanceID] = normalized[instanceID] or {}
|
||||
table.insert(normalized[instanceID], tierID)
|
||||
end
|
||||
else
|
||||
normalized[tierID] = instanceList
|
||||
end
|
||||
end
|
||||
|
||||
return normalized
|
||||
end
|
||||
|
||||
local tiers = data.JOURNALTIER or data.tiers or JOURNALTIER
|
||||
local tierInstances = data.JOURNALTIERXINSTANCE or data.tierInstances or JOURNALTIERXINSTANCE
|
||||
|
||||
JOURNALINSTANCE = data.JOURNALINSTANCE or data.instances or JOURNALINSTANCE
|
||||
JOURNALENCOUNTER = data.JOURNALENCOUNTER or data.encounters or JOURNALENCOUNTER
|
||||
JOURNALENCOUNTERCREATURE = data.JOURNALENCOUNTERCREATURE or data.creatures or JOURNALENCOUNTERCREATURE
|
||||
JOURNALENCOUNTERITEM = data.JOURNALENCOUNTERITEM or data.items or JOURNALENCOUNTERITEM
|
||||
JOURNALENCOUNTERSECTION = data.JOURNALENCOUNTERSECTION or data.sections or JOURNALENCOUNTERSECTION
|
||||
JOURNALTIER = tiers
|
||||
JOURNALTIERXINSTANCE = NormalizeTierInstances(tiers, tierInstances)
|
||||
JOURNAACTUALRAIDS = data.JOURNAACTUALRAIDS or data.actualRaids or JOURNAACTUALRAIDS
|
||||
|
||||
if C_EncounterJournal and C_EncounterJournal.ReloadData then
|
||||
C_EncounterJournal.ReloadData()
|
||||
end
|
||||
end
|
||||
|
||||
MoonWell_ApplyEncounterJournalData = ApplyEncounterJournalData
|
||||
|
||||
local staticData = MoonWellEncounterJournalData or EncounterJournalData
|
||||
if staticData then
|
||||
ApplyEncounterJournalData(staticData)
|
||||
end
|
||||
|
||||
if AIO then
|
||||
local Handler = AIO.AddHandlers("MoonWellEncounterJournal", {})
|
||||
|
||||
function Handler.SetData(_, data)
|
||||
ApplyEncounterJournalData(data)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,157 @@
|
||||
-- Hide the Sirus Encounter Journal panels we don't ship: suggested content,
|
||||
-- LootJournal/ItemBrowser, search box, retail-era top tabs. Dungeons + Raids stay.
|
||||
-- The shipped Sirus tab layout assumes all four content tabs are visible; this
|
||||
-- skin repositions the two tabs we keep.
|
||||
|
||||
local function HideFrame(frame)
|
||||
if frame then
|
||||
frame:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
local function HideTabKeepEnabled(tab)
|
||||
if not tab then
|
||||
return
|
||||
end
|
||||
tab:Hide()
|
||||
-- Keep the tab enabled so Sirus EJ_OnShow does not fall back to the
|
||||
-- suggest-tier code path (EJSuggestTab_GetPlayerTierIndex etc).
|
||||
end
|
||||
|
||||
local function ConfigureTierTab(tab, text)
|
||||
if not tab then
|
||||
return
|
||||
end
|
||||
|
||||
tab:SetText(text or "")
|
||||
|
||||
if tab.SetNormalFontObject and GameFontNormal then
|
||||
tab:SetNormalFontObject(GameFontNormal)
|
||||
end
|
||||
if tab.SetHighlightFontObject and GameFontHighlight then
|
||||
tab:SetHighlightFontObject(GameFontHighlight)
|
||||
end
|
||||
if tab.SetDisabledFontObject and GameFontNormal then
|
||||
tab:SetDisabledFontObject(GameFontNormal)
|
||||
end
|
||||
|
||||
local fontString = tab:GetFontString()
|
||||
if fontString then
|
||||
fontString:SetText(text or "")
|
||||
fontString:ClearAllPoints()
|
||||
fontString:SetPoint("CENTER", tab, "CENTER", 0, -3)
|
||||
if fontString.SetDrawLayer then
|
||||
fontString:SetDrawLayer("OVERLAY", 7)
|
||||
end
|
||||
end
|
||||
|
||||
tab:SetWidth(math.max((tab:GetTextWidth() or 0) + 28, 92))
|
||||
end
|
||||
|
||||
local function PositionVisibleTierTabs(instanceSelect)
|
||||
local guidebookTab = instanceSelect and instanceSelect.guidebookTab
|
||||
local dungeonsTab = instanceSelect and instanceSelect.dungeonsTab
|
||||
local raidsTab = instanceSelect and instanceSelect.raidsTab
|
||||
if not dungeonsTab or not raidsTab then
|
||||
return
|
||||
end
|
||||
|
||||
ConfigureTierTab(guidebookTab, GUIDEBOOK or "Guidebook")
|
||||
ConfigureTierTab(dungeonsTab, INSTANCES or "Instances")
|
||||
ConfigureTierTab(raidsTab, RAIDS or "Raids")
|
||||
|
||||
dungeonsTab:ClearAllPoints()
|
||||
dungeonsTab:SetPoint("BOTTOMLEFT", instanceSelect, "TOPLEFT", 150, -45)
|
||||
|
||||
raidsTab:ClearAllPoints()
|
||||
raidsTab:SetPoint("BOTTOMLEFT", dungeonsTab, "BOTTOMRIGHT", 22, 0)
|
||||
end
|
||||
|
||||
local function GetVisibleSelectedTabID(instanceSelect)
|
||||
if not instanceSelect then
|
||||
return
|
||||
end
|
||||
|
||||
local selectedTab = instanceSelect.selectedTab
|
||||
local dungeonsTab = instanceSelect.dungeonsTab
|
||||
local raidsTab = instanceSelect.raidsTab
|
||||
|
||||
if raidsTab and selectedTab == raidsTab.id then
|
||||
return selectedTab
|
||||
end
|
||||
|
||||
if dungeonsTab then
|
||||
dungeonsTab.id = dungeonsTab.id or dungeonsTab:GetID()
|
||||
return dungeonsTab.id
|
||||
end
|
||||
end
|
||||
|
||||
local function SelectVisibleInstanceTab(journal)
|
||||
local instanceSelect = journal and journal.instanceSelect
|
||||
if not instanceSelect then
|
||||
return
|
||||
end
|
||||
|
||||
local selectedTab = GetVisibleSelectedTabID(instanceSelect)
|
||||
if selectedTab then
|
||||
instanceSelect.selectedTab = selectedTab
|
||||
end
|
||||
|
||||
if journal:IsShown()
|
||||
and instanceSelect:IsShown()
|
||||
and (not journal.encounter or not journal.encounter:IsShown())
|
||||
and selectedTab
|
||||
and EJ_ContentTab_Select
|
||||
then
|
||||
EJ_ContentTab_Select(selectedTab)
|
||||
end
|
||||
end
|
||||
|
||||
local function ApplyDungeonAndRaidOnlySkin()
|
||||
local journal = EncounterJournal
|
||||
if not journal then
|
||||
return
|
||||
end
|
||||
|
||||
-- Sirus EJ_OnLoad sets numTabs at the very end; if anything earlier in
|
||||
-- OnLoad bails out, PanelTemplates_UpdateTabs blows up later with a nil
|
||||
-- for-limit when OnShow calls PanelTemplates_SetTab. Guard it.
|
||||
if journal.numTabs == nil then
|
||||
journal.numTabs = 4
|
||||
end
|
||||
|
||||
HideFrame(journal.searchBox)
|
||||
HideFrame(journal.searchResults)
|
||||
HideFrame(journal.suggestFrame)
|
||||
HideFrame(journal.LootJournal)
|
||||
HideFrame(journal.LootJournalItems)
|
||||
HideFrame(journal.TutorialButton)
|
||||
|
||||
for index = 1, 4 do
|
||||
HideFrame(_G["EncounterJournalTab" .. index])
|
||||
end
|
||||
|
||||
local instanceSelect = journal.instanceSelect
|
||||
if instanceSelect then
|
||||
HideTabKeepEnabled(instanceSelect.suggestTab)
|
||||
HideTabKeepEnabled(instanceSelect.LootJournalTab)
|
||||
|
||||
-- Sirus EJ_OnShow forwards instanceSelect.selectedTab into
|
||||
-- EJ_ContentTab_Select; if it's nil the tab loop matches nothing and
|
||||
-- selectedTab.selectedGlow blows up. Default to the dungeons tab.
|
||||
local dungeonsTab = instanceSelect.dungeonsTab
|
||||
if dungeonsTab then
|
||||
dungeonsTab.id = dungeonsTab.id or dungeonsTab:GetID()
|
||||
end
|
||||
|
||||
PositionVisibleTierTabs(instanceSelect)
|
||||
SelectVisibleInstanceTab(journal)
|
||||
end
|
||||
end
|
||||
|
||||
MoonWell_EncounterJournalApplyDungeonMode = ApplyDungeonAndRaidOnlySkin
|
||||
|
||||
if EncounterJournal then
|
||||
ApplyDungeonAndRaidOnlySkin()
|
||||
EncounterJournal:HookScript("OnShow", ApplyDungeonAndRaidOnlySkin)
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Font name="QuestFont_Super_Huge" font="Fonts\MORPHEUS.ttf" virtual="true">
|
||||
<FontHeight val="24"/>
|
||||
<Color r="1" g=".82" b="0"/>
|
||||
</Font>
|
||||
<Font name="QuestFont_Super_Huge2" font="Fonts\MORPHEUS.ttf" virtual="true">
|
||||
<FontHeight val="26"/>
|
||||
<Shadow>
|
||||
<Offset x="2" y="-2"/>
|
||||
<Color r="0" g="0" b="0"/>
|
||||
</Shadow>
|
||||
</Font>
|
||||
<Font name="GameFontNormalMed1" inherits="GameFontNormal" virtual="true">
|
||||
<FontHeight val="12"/>
|
||||
<Shadow>
|
||||
<Offset x="1" y="-1"/>
|
||||
<Color r="0" g="0" b="0"/>
|
||||
</Shadow>
|
||||
<Color r="1" g=".82" b="0"/>
|
||||
</Font>
|
||||
<Font name="QuestFont22" font="Fonts\MORPHEUS.ttf" virtual="true">
|
||||
<FontHeight val="22"/>
|
||||
<Color r="1" g=".82" b="0"/>
|
||||
</Font>
|
||||
<Font name="QuestFont_Enormous" font="Fonts\MORPHEUS.ttf" virtual="true">
|
||||
<FontHeight val="30"/>
|
||||
<Color r="1" g=".82" b="0"/>
|
||||
</Font>
|
||||
<Font name="DestinyFontHuge" font="Fonts\MORPHEUS.ttf" virtual="true">
|
||||
<FontHeight val="32"/>
|
||||
<Color r="1" g=".82" b="0"/>
|
||||
</Font>
|
||||
</Ui>
|
||||
@@ -0,0 +1,131 @@
|
||||
-- Minimal Frame metatable extensions used by Sirus EJ. Replaces Sirus
|
||||
-- SharedExtendedMethods.lua, which clobbers SetUnit/PlayerModel methods with
|
||||
-- retail-only API (ResetAllSpells, SetSpell, SetUnitRaw, GetDisplayIDs,
|
||||
-- C_CustomizationsCollection.*) and breaks TabardFrame/DressUpFrame on 3.3.5.
|
||||
|
||||
local function getMeta(objType)
|
||||
local frame = CreateFrame(objType)
|
||||
frame:Hide()
|
||||
return getmetatable(frame).__index
|
||||
end
|
||||
|
||||
local function RegisterCustomEventNoop() end
|
||||
local function UnregisterCustomEventNoop() end
|
||||
|
||||
local function SetShown(self, shown)
|
||||
if shown then
|
||||
self:Show()
|
||||
else
|
||||
self:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
local function SetParentArray(self, arrayName)
|
||||
local parent = self:GetParent()
|
||||
if not parent or not arrayName then
|
||||
return
|
||||
end
|
||||
parent[arrayName] = parent[arrayName] or {}
|
||||
table.insert(parent[arrayName], self)
|
||||
end
|
||||
|
||||
local function SetEnabled(self, enabled)
|
||||
if enabled then
|
||||
self:Enable()
|
||||
else
|
||||
self:Disable()
|
||||
end
|
||||
end
|
||||
|
||||
-- Sirus EJ calls Frame/Button:IsMouseOverEx() (Cataclysm+ retail-only).
|
||||
-- Map to the 3.3.5 IsMouseOver().
|
||||
local function IsMouseOverEx(self)
|
||||
return self:IsMouseOver()
|
||||
end
|
||||
|
||||
local metas = {
|
||||
getMeta("Frame"),
|
||||
getMeta("Button"),
|
||||
getMeta("CheckButton"),
|
||||
getMeta("EditBox"),
|
||||
getMeta("Slider"),
|
||||
getMeta("StatusBar"),
|
||||
getMeta("ScrollFrame"),
|
||||
getMeta("MessageFrame"),
|
||||
getMeta("ScrollingMessageFrame"),
|
||||
}
|
||||
|
||||
for _, meta in ipairs(metas) do
|
||||
if type(meta) == "table" then
|
||||
if not meta.RegisterCustomEvent then
|
||||
meta.RegisterCustomEvent = RegisterCustomEventNoop
|
||||
end
|
||||
if not meta.UnregisterCustomEvent then
|
||||
meta.UnregisterCustomEvent = UnregisterCustomEventNoop
|
||||
end
|
||||
if not meta.SetShown then
|
||||
meta.SetShown = SetShown
|
||||
end
|
||||
if not meta.SetParentArray then
|
||||
meta.SetParentArray = SetParentArray
|
||||
end
|
||||
if not meta.SetEnabled then
|
||||
meta.SetEnabled = SetEnabled
|
||||
end
|
||||
if not meta.IsMouseOverEx then
|
||||
meta.IsMouseOverEx = IsMouseOverEx
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Sirus EJ calls FontString:IsTruncated() (Cataclysm+) to detect overflow for
|
||||
-- header tooltips. Always return false on 3.3.5 — we just don't show the
|
||||
-- "expand" tooltip.
|
||||
do
|
||||
local frame = CreateFrame("Frame")
|
||||
local fs = frame:CreateFontString()
|
||||
local meta = getmetatable(fs)
|
||||
local methods = meta and meta.__index
|
||||
if type(methods) == "table" and not methods.IsTruncated then
|
||||
methods.IsTruncated = function() return false end
|
||||
end
|
||||
end
|
||||
|
||||
-- Sirus EJ description fields are SimpleHTML frames; the EJ logic does
|
||||
-- `self.description:GetText()` (Cataclysm+ on SimpleHTML). 3.3.5 SimpleHTML
|
||||
-- only has SetText, no GetText. Hook SetText to remember the last text and
|
||||
-- expose GetText that returns it.
|
||||
do
|
||||
local ok, simple = pcall(CreateFrame, "SimpleHTML")
|
||||
if ok and simple then
|
||||
local meta = getmetatable(simple)
|
||||
local methods = meta and meta.__index
|
||||
if type(methods) == "table" then
|
||||
local origSetText = methods.SetText
|
||||
if origSetText and not methods.GetText then
|
||||
methods.SetText = function(self, text, ...)
|
||||
self.__moonwellText = text
|
||||
return origSetText(self, text, ...)
|
||||
end
|
||||
methods.GetText = function(self)
|
||||
return self.__moonwellText
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Sirus EJ calls Texture:SetPortrait(displayID) for boss/creature portraits.
|
||||
-- 3.3.5 has no SetPortraitTextureFromCreatureDisplayID equivalent, so leave
|
||||
-- the texture blank — the static UI-EJ-BOSS-* icon set elsewhere is enough.
|
||||
do
|
||||
local frame = CreateFrame("Frame")
|
||||
local texture = frame:CreateTexture()
|
||||
local meta = getmetatable(texture)
|
||||
local methods = meta and meta.__index
|
||||
if type(methods) == "table" and not methods.SetPortrait then
|
||||
methods.SetPortrait = function(self)
|
||||
self:SetTexture(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
-- Force C_EncounterJournal to copy the freshly loaded JOURNAL* globals into its
|
||||
-- internal locals. Without this the EJ data API returns nil and the instance
|
||||
-- list comes up empty.
|
||||
|
||||
if C_EncounterJournal and C_EncounterJournal.ReloadData then
|
||||
C_EncounterJournal.ReloadData()
|
||||
end
|
||||
|
||||
-- EJ_GetLootFilter returns nil until a class filter is set; Sirus
|
||||
-- EncounterJournal_UpdateFilterString does `if classID > 0 then`, which blows
|
||||
-- up when classID is nil. Coerce to 0.
|
||||
if EJ_GetLootFilter then
|
||||
local _GetLootFilter = EJ_GetLootFilter
|
||||
function EJ_GetLootFilter()
|
||||
return _GetLootFilter() or 0
|
||||
end
|
||||
end
|
||||
|
||||
-- Sirus C_AdventureJournal sometimes fails to fully initialize on 3.3.5
|
||||
-- because PRIVATE.Initialize() inside Utils/C_AdventureJournal.lua hits an
|
||||
-- unsupported API mid-way and aborts before `C_AdventureJournal = {}` is
|
||||
-- reached. Provide a no-op stub so suggestFrame OnShow doesn't blow up.
|
||||
C_AdventureJournal = C_AdventureJournal or {}
|
||||
local function noop() end
|
||||
C_AdventureJournal.UpdateSuggestions = C_AdventureJournal.UpdateSuggestions or noop
|
||||
C_AdventureJournal.GetPrimaryOffset = C_AdventureJournal.GetPrimaryOffset or function() return 0 end
|
||||
C_AdventureJournal.SetPrimaryOffset = C_AdventureJournal.SetPrimaryOffset or noop
|
||||
C_AdventureJournal.GetNumAvailableSuggestions = C_AdventureJournal.GetNumAvailableSuggestions or function() return 0 end
|
||||
C_AdventureJournal.GetSuggestion = C_AdventureJournal.GetSuggestion or function() end
|
||||
C_AdventureJournal.CanBeShown = C_AdventureJournal.CanBeShown or function() return true end
|
||||
C_AdventureJournal.ReloadData = C_AdventureJournal.ReloadData or noop
|
||||
C_AdventureJournal.ActivateEntry = C_AdventureJournal.ActivateEntry or noop
|
||||
C_AdventureJournal.GetSuggestions = C_AdventureJournal.GetSuggestions or function(out)
|
||||
if type(out) == "table" then
|
||||
for k in pairs(out) do out[k] = nil end
|
||||
end
|
||||
return out or {}
|
||||
end
|
||||
C_AdventureJournal.GetReward = C_AdventureJournal.GetReward or function() end
|
||||
C_AdventureJournal.GetPrimaryReward = C_AdventureJournal.GetPrimaryReward or function() end
|
||||
C_AdventureJournal.GetSecondaryReward = C_AdventureJournal.GetSecondaryReward or function() end
|
||||
@@ -0,0 +1,69 @@
|
||||
-- Explicit entry point for 3.3.5 clients, which do not have the retail
|
||||
-- EncounterJournal micro button.
|
||||
|
||||
local function MoonWell_ToggleEncounterJournal()
|
||||
if not EncounterJournal then
|
||||
return
|
||||
end
|
||||
|
||||
if EncounterJournal:IsShown() then
|
||||
HideUIPanel(EncounterJournal)
|
||||
return
|
||||
end
|
||||
|
||||
if MoonWell_EncounterJournalApplyDungeonMode then
|
||||
MoonWell_EncounterJournalApplyDungeonMode()
|
||||
end
|
||||
|
||||
ShowUIPanel(EncounterJournal)
|
||||
|
||||
if EJPlayerGuide_OpenFrame then
|
||||
EJPlayerGuide_OpenFrame()
|
||||
elseif EncounterJournal.instanceSelect and EncounterJournal.instanceSelect.guideTab and EJ_ContentTab_Select then
|
||||
EJ_ContentTab_Select(EncounterJournal.instanceSelect.guideTab:GetID())
|
||||
end
|
||||
end
|
||||
|
||||
SLASH_MOONWELLENCOUNTERJOURNAL1 = "/ej"
|
||||
SLASH_MOONWELLENCOUNTERJOURNAL2 = "/journal"
|
||||
SLASH_MOONWELLENCOUNTERJOURNAL3 = "/encounterjournal"
|
||||
SlashCmdList.MOONWELLENCOUNTERJOURNAL = MoonWell_ToggleEncounterJournal
|
||||
|
||||
MoonWell_ToggleEncounterJournal = MoonWell_ToggleEncounterJournal
|
||||
|
||||
local function MoonWell_EncounterJournalMicroButton_OnEnter(self)
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine("Путеводитель по приключениям", 1, 1, 1)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
|
||||
local function MoonWell_CreateEncounterJournalMicroButton()
|
||||
local button = EncounterJournalMicroButton
|
||||
if not button then
|
||||
local iconTexture = "Interface\\EncounterJournal\\UI-EJ-PortraitIcon"
|
||||
button = CreateFrame("Button", "EncounterJournalMicroButton", MainMenuBarArtFrame or UIParent)
|
||||
button:SetSize(32, 40)
|
||||
button:SetPoint("BOTTOMLEFT", LFDMicroButton or MainMenuMicroButton or HelpMicroButton or UIParent, "BOTTOMRIGHT", 0, 0)
|
||||
button:RegisterForClicks("LeftButtonUp")
|
||||
button:SetNormalTexture(iconTexture)
|
||||
button:SetPushedTexture(iconTexture)
|
||||
button:SetDisabledTexture(iconTexture)
|
||||
button:SetHighlightTexture(iconTexture)
|
||||
button:SetPushedTextOffset(1, -1)
|
||||
|
||||
local highlight = button:GetHighlightTexture()
|
||||
if highlight then
|
||||
highlight:SetBlendMode("ADD")
|
||||
highlight:SetAlpha(0.55)
|
||||
end
|
||||
end
|
||||
|
||||
button:SetScript("OnClick", MoonWell_ToggleEncounterJournal)
|
||||
button:SetScript("OnEnter", MoonWell_EncounterJournalMicroButton_OnEnter)
|
||||
button:SetScript("OnLeave", GameTooltip_Hide)
|
||||
button:Show()
|
||||
|
||||
return button
|
||||
end
|
||||
|
||||
MoonWell_CreateEncounterJournalMicroButton()
|
||||
@@ -0,0 +1,24 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<!--
|
||||
Sirus EncounterJournalScrollBarTemplate inherits MinimalScrollBarTemplate
|
||||
and references self.trackBG inside its inline OnLoad. On 3.3.5 the
|
||||
parentKey="trackBG" attribute on the inherited template does not
|
||||
propagate, leaving self.trackBG nil and crashing OnLoad. Define the
|
||||
template here ourselves with trackBG as a direct child texture, before
|
||||
Sirus Custom_EncounterJournal.xml gets a chance to register its own.
|
||||
-->
|
||||
<Slider name="EncounterJournalScrollBarTemplate" inherits="MinimalScrollBarTemplate" parentKey="ScrollBar" virtual="true">
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" x="0" y="-17"/>
|
||||
<Anchor point="BOTTOMRIGHT" x="0" y="17"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture parentKey="trackBG" setAllPoints="true">
|
||||
<Color r="0.2" g="0.13" b="0.08" a="0.25"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Slider>
|
||||
</Ui>
|
||||
@@ -0,0 +1,40 @@
|
||||
-- Sirus EJ XML for lootScroll/searchResults.scrollFrame omits the scrollUp /
|
||||
-- scrollDown buttons that the stock 3.3.5 HybridScrollFrame implementation
|
||||
-- requires (HybridScrollFrame_Update / HybridScrollFrame_UpdateButtonStates
|
||||
-- index them directly). Wire up no-op buttons so the stock helpers don't
|
||||
-- index nil and the rest of the EJ flow works.
|
||||
|
||||
local function AttachFakeScrollButtons(scrollFrame)
|
||||
if not scrollFrame then
|
||||
return
|
||||
end
|
||||
if not scrollFrame.scrollUp then
|
||||
local btn = CreateFrame("Button", nil, scrollFrame)
|
||||
btn:Hide()
|
||||
btn:SetSize(1, 1)
|
||||
btn:SetPoint("TOPLEFT")
|
||||
scrollFrame.scrollUp = btn
|
||||
end
|
||||
if not scrollFrame.scrollDown then
|
||||
local btn = CreateFrame("Button", nil, scrollFrame)
|
||||
btn:Hide()
|
||||
btn:SetSize(1, 1)
|
||||
btn:SetPoint("BOTTOMLEFT")
|
||||
scrollFrame.scrollDown = btn
|
||||
end
|
||||
if scrollFrame.scrollBar and not scrollFrame.scrollBar.thumbTexture then
|
||||
scrollFrame.scrollBar.thumbTexture = scrollFrame.scrollBar:GetThumbTexture()
|
||||
or scrollFrame.scrollBar:CreateTexture(nil, "ARTWORK")
|
||||
end
|
||||
if scrollFrame.scrollBar and not scrollFrame.scrollBar.trackBG then
|
||||
scrollFrame.scrollBar.trackBG = scrollFrame.scrollBar:CreateTexture(nil, "BACKGROUND")
|
||||
end
|
||||
end
|
||||
|
||||
if EncounterJournal then
|
||||
AttachFakeScrollButtons(EncounterJournal.encounter
|
||||
and EncounterJournal.encounter.info
|
||||
and EncounterJournal.encounter.info.lootScroll)
|
||||
AttachFakeScrollButtons(EncounterJournal.searchResults
|
||||
and EncounterJournal.searchResults.scrollFrame)
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
-- Restrict Sirus EJ data tables to expansions that exist on 3.3.5a (Classic, TBC, WotLK).
|
||||
-- Must run AFTER Generated_EncounterJournal.lua (where the globals are populated) and BEFORE
|
||||
-- Utils/C_EncounterJournal.lua reads them into locals on first ReloadData.
|
||||
|
||||
local ALLOWED_TIER_IDS = {
|
||||
[68] = true, -- Classic
|
||||
[70] = true, -- Burning Crusade
|
||||
[72] = true, -- Wrath of the Lich King
|
||||
}
|
||||
|
||||
local TIER_FIELD_ID = 1
|
||||
|
||||
if type(JOURNALTIER) == "table" then
|
||||
local filtered = {}
|
||||
for _, tierInfo in ipairs(JOURNALTIER) do
|
||||
if ALLOWED_TIER_IDS[tierInfo[TIER_FIELD_ID]] then
|
||||
filtered[#filtered + 1] = tierInfo
|
||||
end
|
||||
end
|
||||
JOURNALTIER = filtered
|
||||
end
|
||||
|
||||
if type(JOURNALTIERXINSTANCE) == "table" then
|
||||
for instanceID, tierList in pairs(JOURNALTIERXINSTANCE) do
|
||||
local kept
|
||||
for _, tierID in ipairs(tierList) do
|
||||
if ALLOWED_TIER_IDS[tierID] then
|
||||
kept = kept or {}
|
||||
kept[#kept + 1] = tierID
|
||||
end
|
||||
end
|
||||
if kept then
|
||||
JOURNALTIERXINSTANCE[instanceID] = kept
|
||||
else
|
||||
JOURNALTIERXINSTANCE[instanceID] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Keep tier tables available for the 3.3.5a dungeon-list shim.
|
||||
-- C_EncounterJournal imports Sirus globals into locals and clears the globals.
|
||||
|
||||
MoonWell_EncounterJournalTiers = CopyTable(JOURNALTIER or {})
|
||||
MoonWell_EncounterJournalTierInstances = CopyTable(JOURNALTIERXINSTANCE or {})
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
ADVENTURE_JOURNAL = "Путеводитель по приключениям"
|
||||
AJ_ACTION_TEXT_JOIN_BATTLE = "Вступить в бой"
|
||||
AJ_ACTION_TEXT_JOIN_GROUP = "Вступить в группу"
|
||||
AJ_ACTION_TEXT_OPEN_EJ = "Открыть журнал"
|
||||
AJ_ACTION_TEXT_SHOW_QUEST = "Открыть задания"
|
||||
AJ_ACTION_TEXT_START_HUNT = "Открыть охоту"
|
||||
AJ_ACTION_TEXT_START_QUEST = "Начать задание"
|
||||
AJ_LFG_REWARD_DEFAULT_IRANGE_TEXT = "%s: здесь можно получить предметы экипировки %i - %i уровней."
|
||||
AJ_LFG_REWARD_DEFAULT_TEXT = "%s: здесь можно получить предметы экипировки %i-го уровня."
|
||||
AJ_LFG_REWARD_DIFFICULTY_IRANGE_TEXT = "%s (%s): здесь можно получить предметы экипировки %i - %i уровней."
|
||||
AJ_LFG_REWARD_DIFFICULTY_TEXT = "%s (%s): здесь можно получить предметы экипировки %i-го уровня."
|
||||
AJ_PRIMARY_REWARD_TEXT = "Награда:"
|
||||
AJ_REWARD_CLICK_TEXT = "Щелкните для просмотра всех наград."
|
||||
AJ_SAMPLE_REWARD_TEXT = "\n\nПример награды:"
|
||||
AJ_SUGGESTED_CONTENT_TAB = "Рекомендуемый контент"
|
||||
EJ_CLASS_FILTER = "Фильтр по классу: %s"
|
||||
EJ_FILTER_ALL_CLASS = "Все классы"
|
||||
EJ_INSTANCE_REQUIREMENT_ACHIEVEMENTS = "Достижения:"
|
||||
EJ_INSTANCE_REQUIREMENT_DIFFICULTY = "Сложность: %s"
|
||||
EJ_INSTANCE_REQUIREMENT_ITEM_LEVEL = "Средний уровень предметов: %s"
|
||||
EJ_INSTANCE_REQUIREMENT_LABLE = "Требования:"
|
||||
EJ_INSTANCE_REQUIREMENT_LEVEL = "Уровень: %s"
|
||||
EJ_INSTANCE_REQUIREMENT_LEVEL_RANGE = "Уровень: %s-%s"
|
||||
EJ_INSTANCE_REQUIREMENT_NONE = "Вход свободный"
|
||||
EJ_INSTANCE_REQUIREMENT_QUESTS = "Задания:"
|
||||
EJ_INSTANCE_REQUIREMENT_RAID_GROUP = "Для входа требуется быть участником рейдовой группы"
|
||||
EJ_SET_ITEM_LEVEL = "|cffcc4040[%s]|r Уровень предмета: %d"
|
||||
ENCOUNTER_JOURNAL_ABILITY = "Способность"
|
||||
ENCOUNTER_JOURNAL_ENCOUNTER = "Босс"
|
||||
ENCOUNTER_JOURNAL_ENCOUNTER_ADD = "Помощник"
|
||||
ENCOUNTER_JOURNAL_FIND_GROUP = "Найти\nгруппу"
|
||||
ENCOUNTER_JOURNAL_FIND_RAID = "Найти\nрейд"
|
||||
ENCOUNTER_JOURNAL_INSTANCE = "Подземелье"
|
||||
ENCOUNTER_JOURNAL_ITEM = "Предмет"
|
||||
ENCOUNTER_JOURNAL_SEARCH_RESULTS = "Результаты поиска для \"%s\" (%d)"
|
||||
ENCOUNTER_JOURNAL_SHOW_MAP = "Показать\nкарту"
|
||||
ENCOUNTER_JOURNAL_SHOW_SEARCH_RESULTS = "Показать все результаты: %d"
|
||||
LOOT_JOURNAL_ITEM_SETS = "Комплекты"
|
||||
ENCOUNTER_JOURNAL_NAVIGATION_HOME = "Главная"
|
||||
|
||||
-- Путеводитель — вкладка
|
||||
MW_PLAYER_GUIDE = "Путеводитель"
|
||||
MW_PLAYER_GUIDE_REFRESH = "Обновить"
|
||||
|
||||
-- Заголовки секций
|
||||
MW_PLAYER_GUIDE_OBJECTIVES = "Задачи этапа"
|
||||
MW_PLAYER_GUIDE_RECS = "Подходящие подземелья"
|
||||
MW_PLAYER_GUIDE_REWARDS = "Награды за этап"
|
||||
|
||||
-- Шапка этапа
|
||||
MW_PLAYER_GUIDE_STAGE_OF = "ЭТАП %d ИЗ %d"
|
||||
MW_PLAYER_GUIDE_STAGE_PROGRESS = "ПРОГРЕСС ЭТАПА"
|
||||
|
||||
-- Пустое состояние
|
||||
MW_PLAYER_GUIDE_EMPTY_TITLE = "Путеводитель ещё не открыт"
|
||||
MW_PLAYER_GUIDE_EMPTY_BODY = "Данные о вашем прогрессе ещё не получены с сервера. " ..
|
||||
"Это может занять несколько секунд после входа в игру."
|
||||
|
||||
-- Загрузка
|
||||
MW_PLAYER_GUIDE_LOADING_TITLE = "ЗАПРАШИВАЕМ ХРОНИКИ"
|
||||
MW_PLAYER_GUIDE_LOADING_BODY = "Ожидание данных от Путеводителя"
|
||||
|
||||
-- Caps-плашки типов целей
|
||||
MW_PG_TYPE_LEVEL = "УРОВЕНЬ"
|
||||
MW_PG_TYPE_QUEST = "ЗАДАНИЕ"
|
||||
MW_PG_TYPE_QUEST_CHAIN = "ЦЕПОЧКА"
|
||||
MW_PG_TYPE_DUNGEON = "ПОДЗЕМЕЛЬЕ"
|
||||
MW_PG_TYPE_RAID = "РЕЙД"
|
||||
MW_PG_TYPE_BOSS = "БОСС"
|
||||
MW_PG_TYPE_ACHIEVEMENT = "ДОСТИЖЕНИЕ"
|
||||
MW_PG_TYPE_REPUTATION = "РЕПУТАЦИЯ"
|
||||
MW_PG_TYPE_PROFESSION = "ПРОФЕССИЯ"
|
||||
MW_PG_TYPE_ITEM = "ПРЕДМЕТ"
|
||||
MW_PG_TYPE_CURRENCY = "ВАЛЮТА"
|
||||
MW_PG_TYPE_CUSTOM = "ЗАДАЧА"
|
||||
@@ -0,0 +1,128 @@
|
||||
local securecall = securecall
|
||||
local ipairs = ipairs
|
||||
local next = next
|
||||
local type = type
|
||||
local strsplit = string.split
|
||||
|
||||
local ExecuteFrameScript = ExecuteFrameScript
|
||||
local GetFramesRegisteredForEvent = GetFramesRegisteredForEvent
|
||||
local UnitName = UnitName
|
||||
local UnitTokenFromGUID = UnitTokenFromGUID
|
||||
|
||||
local REGISTERED_CUSTOM_EVENTS = {}
|
||||
local REGISTERED_CUSTOM_EVENTS_ALL
|
||||
|
||||
local function SecureNext(elements, key)
|
||||
return securecall(next, elements, key)
|
||||
end
|
||||
|
||||
function FireClientEvent(event, ...)
|
||||
for _, frame in SecureNext, {GetFramesRegisteredForEvent(event)} do
|
||||
securecall(ExecuteFrameScript, frame, "OnEvent", event, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function FireCustomClientEvent(event, ...)
|
||||
if REGISTERED_CUSTOM_EVENTS[event] then
|
||||
for frame in SecureNext, REGISTERED_CUSTOM_EVENTS[event] do
|
||||
securecall(ExecuteFrameScript, frame, "OnEvent", event, ...)
|
||||
end
|
||||
end
|
||||
if REGISTERED_CUSTOM_EVENTS_ALL then
|
||||
for frame in SecureNext, REGISTERED_CUSTOM_EVENTS_ALL do
|
||||
securecall(ExecuteFrameScript, frame, "OnEvent", event, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local eventRegistrar = CreateFrame("Frame")
|
||||
eventRegistrar:SetScript("OnAttributeChanged", function(this, name, value)
|
||||
if name == "1" then
|
||||
if not REGISTERED_CUSTOM_EVENTS[value] then
|
||||
REGISTERED_CUSTOM_EVENTS[value] = {}
|
||||
end
|
||||
REGISTERED_CUSTOM_EVENTS[value][this:GetAttribute("f")] = true
|
||||
elseif name == "0" then
|
||||
if REGISTERED_CUSTOM_EVENTS[value] then
|
||||
REGISTERED_CUSTOM_EVENTS[value][this:GetAttribute("f")] = nil
|
||||
end
|
||||
elseif name == "2" then
|
||||
REGISTERED_CUSTOM_EVENTS_ALL[value] = true
|
||||
elseif name == "3" then
|
||||
REGISTERED_CUSTOM_EVENTS_ALL[value] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
function RegisterCustomEvent(self, event)
|
||||
eventRegistrar:SetAttribute("f", self)
|
||||
eventRegistrar:SetAttribute("1", event)
|
||||
end
|
||||
|
||||
function UnregisterCustomEvent(self, event)
|
||||
eventRegistrar:SetAttribute("f", self)
|
||||
eventRegistrar:SetAttribute("0", event)
|
||||
end
|
||||
|
||||
function RegisterAllCustomEvents(self)
|
||||
if not REGISTERED_CUSTOM_EVENTS_ALL then
|
||||
REGISTERED_CUSTOM_EVENTS_ALL = {}
|
||||
end
|
||||
eventRegistrar:SetAttribute("2", self)
|
||||
end
|
||||
|
||||
function UnregisterAllCustomEvents(self)
|
||||
if not REGISTERED_CUSTOM_EVENTS_ALL then
|
||||
return
|
||||
end
|
||||
eventRegistrar:SetAttribute("3", self)
|
||||
end
|
||||
|
||||
function FireCustomClientUnitGroupEvent(event, unitGUID, ...)
|
||||
if event and REGISTERED_CUSTOM_EVENTS[event] and unitGUID then
|
||||
local units = {UnitTokenFromGUID(unitGUID)}
|
||||
if #units > 0 then
|
||||
for frame in SecureNext, REGISTERED_CUSTOM_EVENTS[event] do
|
||||
for _, unit in ipairs(units) do
|
||||
securecall(ExecuteFrameScript, frame, "OnEvent", event, unit, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
EventHandler = setmetatable(
|
||||
{
|
||||
events = {}, -- Original events
|
||||
listeners = {}, -- New tables for handling events outside og EventHandler
|
||||
RegisterListener = function(self, listener)
|
||||
self.listeners[listener] = true
|
||||
end,
|
||||
Handle = function(self, opcode, message, unk, sender)
|
||||
if sender == UnitName("player") then
|
||||
for listener in SecureNext, self.listeners do
|
||||
if listener[opcode] then
|
||||
securecall(listener[opcode], listener, message)
|
||||
end
|
||||
end
|
||||
|
||||
if self.events[opcode] then
|
||||
securecall(self.events[opcode], self, message)
|
||||
end
|
||||
end
|
||||
end
|
||||
},
|
||||
{
|
||||
__newindex = function(self, key, value)
|
||||
if type(value) == "function" then
|
||||
self.events[key] = value
|
||||
end
|
||||
rawset(self, key, value)
|
||||
end
|
||||
}
|
||||
)
|
||||
|
||||
local EventHandlerFrame = CreateFrame("Frame")
|
||||
EventHandlerFrame:RegisterEvent("CHAT_MSG_ADDON")
|
||||
EventHandlerFrame:SetScript("OnEvent", function(self, event, opcode, message, unk, sender)
|
||||
EventHandler:Handle(opcode, message, unk, sender)
|
||||
end)
|
||||
@@ -0,0 +1,427 @@
|
||||
--[[-----------------------------------------------------------------------------------------------
|
||||
For a hybrid scroll frame with buttons of varying size, set .dynamic on the scroll frame
|
||||
to be a function which will take the offset and return:
|
||||
1. how many buttons the offset is completely past
|
||||
2. how many pixels the offset is into the topmost button
|
||||
So with buttons of size 20, .dynamic(0) should return 0,0 and .dynamic(34) should return 1,14
|
||||
-----------------------------------------------------------------------------------------------]]--
|
||||
|
||||
local round = function (num) return math.floor(num + .5); end
|
||||
|
||||
local function HybridScrollFrame_SetVerticalScrollOffset(self, offset)
|
||||
if self.SetVerticalScroll then
|
||||
self:SetVerticalScroll(offset);
|
||||
end
|
||||
end
|
||||
|
||||
local function HybridScrollFrame_UpdateScrollChildRectSafe(self)
|
||||
if self.UpdateScrollChildRect then
|
||||
self:UpdateScrollChildRect();
|
||||
end
|
||||
end
|
||||
|
||||
local function HybridScrollFrame_GetScrollFrame(self)
|
||||
if not self then
|
||||
return self;
|
||||
end
|
||||
if self.SetVerticalScroll then
|
||||
return self;
|
||||
end
|
||||
|
||||
local name = self.GetName and self:GetName();
|
||||
local scrollFrame = self.Container or (name and _G[name.."Container"]);
|
||||
return scrollFrame or self;
|
||||
end
|
||||
|
||||
local function HybridScrollFrame_GetScrollBar(self)
|
||||
if self.scrollBar then
|
||||
return self.scrollBar;
|
||||
end
|
||||
|
||||
local name = self.GetName and self:GetName();
|
||||
local scrollBar = self.ScrollBar or (name and (_G[name.."ScrollBar"] or _G[name.."ContainerScrollBar"]));
|
||||
if scrollBar then
|
||||
self.scrollBar = scrollBar;
|
||||
end
|
||||
return scrollBar;
|
||||
end
|
||||
|
||||
function HybridScrollFrame_OnLoad (self)
|
||||
self:EnableMouse(true);
|
||||
end
|
||||
|
||||
function HybridScrollFrameScrollUp_OnLoad (self)
|
||||
self:GetParent():GetParent().scrollUp = self;
|
||||
self:Disable();
|
||||
self:RegisterForClicks("LeftButtonUp", "LeftButtonDown");
|
||||
self.direction = 1;
|
||||
end
|
||||
|
||||
function HybridScrollFrameScrollDown_OnLoad (self)
|
||||
self:GetParent():GetParent().scrollDown = self;
|
||||
self:Disable();
|
||||
self:RegisterForClicks("LeftButtonUp", "LeftButtonDown");
|
||||
self.direction = -1;
|
||||
end
|
||||
|
||||
function HybridScrollFrame_OnValueChanged (self, value)
|
||||
local scrollFrame = self;
|
||||
if self.SetMinMaxValues and self.GetParent then
|
||||
scrollFrame = self:GetParent();
|
||||
end
|
||||
scrollFrame = HybridScrollFrame_GetScrollFrame(scrollFrame);
|
||||
HybridScrollFrame_SetOffset(scrollFrame, value);
|
||||
HybridScrollFrame_UpdateButtonStates(scrollFrame, value);
|
||||
end
|
||||
|
||||
function HybridScrollFrame_UpdateButtonStates (self, currValue)
|
||||
local scrollBar = HybridScrollFrame_GetScrollBar(self);
|
||||
if ( not scrollBar ) then
|
||||
return;
|
||||
end
|
||||
|
||||
local scrollBarName = scrollBar.GetName and scrollBar:GetName();
|
||||
local scrollUp = self.scrollUp or scrollBar.ScrollUpButton or (scrollBarName and _G[scrollBarName.."ScrollUpButton"]);
|
||||
local scrollDown = self.scrollDown or scrollBar.ScrollDownButton or (scrollBarName and _G[scrollBarName.."ScrollDownButton"]);
|
||||
|
||||
if ( not currValue ) then
|
||||
currValue = scrollBar:GetValue();
|
||||
end
|
||||
|
||||
if ( scrollUp ) then
|
||||
scrollUp:Enable();
|
||||
end
|
||||
if ( scrollDown ) then
|
||||
scrollDown:Enable();
|
||||
end
|
||||
|
||||
local minVal, maxVal = scrollBar:GetMinMaxValues();
|
||||
if ( currValue >= maxVal ) then
|
||||
if ( scrollBar.thumbTexture ) then
|
||||
scrollBar.thumbTexture:Show();
|
||||
end
|
||||
if ( scrollDown ) then
|
||||
scrollDown:Disable()
|
||||
end
|
||||
end
|
||||
if ( currValue <= minVal ) then
|
||||
if ( scrollBar.thumbTexture ) then
|
||||
scrollBar.thumbTexture:Show();
|
||||
end
|
||||
if ( scrollUp ) then
|
||||
scrollUp:Disable();
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function HybridScrollFrame_OnMouseWheel (self, delta, stepSize)
|
||||
self = HybridScrollFrame_GetScrollFrame(self);
|
||||
local scrollBar = HybridScrollFrame_GetScrollBar(self);
|
||||
if ( not scrollBar or not scrollBar:IsVisible() ) then
|
||||
return;
|
||||
end
|
||||
local isEnabled = not scrollBar.IsEnabled or scrollBar:IsEnabled();
|
||||
if ( not isEnabled or isEnabled == 0 ) then
|
||||
return;
|
||||
end
|
||||
|
||||
local minVal, maxVal = scrollBar:GetMinMaxValues();
|
||||
minVal = minVal or 0;
|
||||
maxVal = maxVal or self.range or 0;
|
||||
stepSize = stepSize or self.stepSize or self.buttonHeight or 1;
|
||||
if ( delta == 1 ) then
|
||||
scrollBar:SetValue(max(minVal, scrollBar:GetValue() - stepSize));
|
||||
else
|
||||
scrollBar:SetValue(min(maxVal, scrollBar:GetValue() + stepSize));
|
||||
end
|
||||
end
|
||||
|
||||
function HybridScrollFrameScrollButton_OnUpdate (self, elapsed)
|
||||
self.timeSinceLast = self.timeSinceLast + elapsed;
|
||||
if ( self.timeSinceLast >= ( self.updateInterval or 0.08 ) ) then
|
||||
if ( not IsMouseButtonDown("LeftButton") ) then
|
||||
self:SetScript("OnUpdate", nil);
|
||||
elseif ( self:IsMouseOver() ) then
|
||||
local parent = self.parent or self:GetParent():GetParent();
|
||||
HybridScrollFrame_OnMouseWheel (parent, self.direction, (self.stepSize or parent.buttonHeight/3));
|
||||
self.timeSinceLast = 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function HybridScrollFrameScrollButton_OnClick (self, button, down)
|
||||
local parent = self.parent or self:GetParent():GetParent();
|
||||
|
||||
if ( down ) then
|
||||
if IsMouseButtonDown then
|
||||
self.timeSinceLast = (self.timeToStart or -0.2);
|
||||
self:SetScript("OnUpdate", HybridScrollFrameScrollButton_OnUpdate);
|
||||
end
|
||||
HybridScrollFrame_OnMouseWheel (parent, self.direction);
|
||||
PlaySound("UChatScrollButton");
|
||||
else
|
||||
self:SetScript("OnUpdate", nil);
|
||||
end
|
||||
end
|
||||
|
||||
function HybridScrollFrame_SetPercentageHeight(self, percentageHeight)
|
||||
local offset = percentageHeight * (self.totalHeight or 0);
|
||||
HybridScrollFrame_SetOffset(self, offset);
|
||||
end
|
||||
|
||||
function HybridScrollFrame_GetVisiblePercentage(self)
|
||||
local totalHeight = (self.totalHeight or 0);
|
||||
if totalHeight == 0 then
|
||||
return 0;
|
||||
end
|
||||
|
||||
return self.scrollChild:GetHeight() / totalHeight;
|
||||
end
|
||||
|
||||
function HybridScrollFrame_GetScrollPercentage(self)
|
||||
local scrollableHeight = (self.totalHeight or 0) - self.scrollChild:GetHeight();
|
||||
if scrollableHeight == 0 then
|
||||
return 0;
|
||||
end
|
||||
|
||||
return (self.offset or 0) / scrollableHeight;
|
||||
end
|
||||
|
||||
function HybridScrollFrame_Update (self, totalHeight, displayedHeight)
|
||||
local range = floor(totalHeight - self:GetHeight() + 0.5);
|
||||
local scrollBar = HybridScrollFrame_GetScrollBar(self);
|
||||
if ( range > 0 and scrollBar ) then
|
||||
local minVal, maxVal = scrollBar:GetMinMaxValues();
|
||||
if ( math.floor(scrollBar:GetValue()) >= math.floor(maxVal) ) then
|
||||
scrollBar:SetMinMaxValues(0, range);
|
||||
if ( math.floor(scrollBar:GetValue()) ~= math.floor(range) ) then
|
||||
scrollBar:SetValue(range);
|
||||
else
|
||||
HybridScrollFrame_SetOffset(self, range); -- If we've scrolled to the bottom, we need to recalculate the offset.
|
||||
end
|
||||
else
|
||||
scrollBar:SetMinMaxValues(0, range)
|
||||
end
|
||||
scrollBar:Enable();
|
||||
HybridScrollFrame_UpdateButtonStates(self);
|
||||
scrollBar:Show();
|
||||
elseif ( scrollBar ) then
|
||||
scrollBar:SetValue(0);
|
||||
if ( scrollBar.doNotHide ) then
|
||||
scrollBar:Disable();
|
||||
if ( self.scrollUp ) then
|
||||
self.scrollUp:Disable();
|
||||
end
|
||||
if ( self.scrollDown ) then
|
||||
self.scrollDown:Disable();
|
||||
end
|
||||
if ( scrollBar.thumbTexture ) then
|
||||
scrollBar.thumbTexture:Hide();
|
||||
end
|
||||
else
|
||||
scrollBar:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
self.range = range;
|
||||
self.totalHeight = totalHeight;
|
||||
self.scrollChild:SetHeight(displayedHeight);
|
||||
HybridScrollFrame_UpdateScrollChildRectSafe(self);
|
||||
end
|
||||
|
||||
function HybridScrollFrame_GetOffset (self)
|
||||
return math.floor(self.offset or 0), (self.offset or 0);
|
||||
end
|
||||
|
||||
function HybridScrollFrameScrollChild_OnLoad (self)
|
||||
self:GetParent().scrollChild = self;
|
||||
end
|
||||
|
||||
function HybridScrollFrame_ExpandButton (self, offset, height)
|
||||
self.largeButtonTop = round(offset);
|
||||
self.largeButtonHeight = round(height)
|
||||
local scrollBar = HybridScrollFrame_GetScrollBar(self);
|
||||
HybridScrollFrame_SetOffset(self, scrollBar and scrollBar:GetValue() or 0);
|
||||
end
|
||||
|
||||
function HybridScrollFrame_CollapseButton (self)
|
||||
self.largeButtonTop = nil;
|
||||
self.largeButtonHeight = nil;
|
||||
end
|
||||
|
||||
function HybridScrollFrame_SetOffset (self, offset)
|
||||
local buttons = self.buttons
|
||||
local buttonHeight = self.buttonHeight;
|
||||
local element, overflow;
|
||||
|
||||
if ( not buttonHeight and buttons and buttons[1] ) then
|
||||
buttonHeight = round(buttons[1]:GetHeight());
|
||||
self.buttonHeight = buttonHeight;
|
||||
end
|
||||
if ( not buttonHeight or buttonHeight == 0 ) then
|
||||
self.offset = 0;
|
||||
HybridScrollFrame_SetVerticalScrollOffset(self, 0);
|
||||
return;
|
||||
end
|
||||
|
||||
local scrollHeight = 0;
|
||||
|
||||
local largeButtonTop = self.largeButtonTop
|
||||
if ( self.dynamic ) then --This is for frames where buttons will have different heights
|
||||
if ( offset < buttonHeight ) then
|
||||
-- a little optimization
|
||||
element = 0;
|
||||
scrollHeight = offset;
|
||||
else
|
||||
element, scrollHeight = self.dynamic(offset);
|
||||
end
|
||||
elseif ( largeButtonTop and offset >= largeButtonTop ) then
|
||||
local largeButtonHeight = self.largeButtonHeight;
|
||||
-- Initial offset...
|
||||
element = largeButtonTop / buttonHeight;
|
||||
|
||||
if ( offset >= (largeButtonTop + largeButtonHeight) ) then
|
||||
element = element + 1;
|
||||
|
||||
local leftovers = (offset - (largeButtonTop + largeButtonHeight) );
|
||||
|
||||
element = element + ( leftovers / buttonHeight );
|
||||
overflow = element - math.floor(element);
|
||||
scrollHeight = overflow * buttonHeight;
|
||||
else
|
||||
scrollHeight = math.abs(offset - largeButtonTop);
|
||||
end
|
||||
else
|
||||
element = offset / buttonHeight;
|
||||
overflow = element - math.floor(element);
|
||||
scrollHeight = overflow * buttonHeight;
|
||||
end
|
||||
|
||||
if ( math.floor(self.offset or 0) ~= math.floor(element) and self.update ) then
|
||||
self.offset = element;
|
||||
self:update();
|
||||
else
|
||||
self.offset = element;
|
||||
end
|
||||
|
||||
HybridScrollFrame_SetVerticalScrollOffset(self, scrollHeight);
|
||||
end
|
||||
|
||||
function HybridScrollFrame_CreateButtons (self, buttonTemplate, initialOffsetX, initialOffsetY, initialPoint, initialRelative, offsetX, offsetY, point, relativePoint)
|
||||
local scrollChild = self.scrollChild;
|
||||
local button, buttonHeight, buttons, numButtons;
|
||||
|
||||
local parentName = self:GetName();
|
||||
local buttonName = parentName and (parentName .. "Button") or nil;
|
||||
|
||||
initialPoint = initialPoint or "TOPLEFT";
|
||||
initialRelative = initialRelative or "TOPLEFT";
|
||||
point = point or "TOPLEFT";
|
||||
relativePoint = relativePoint or "BOTTOMLEFT";
|
||||
offsetX = offsetX or 0;
|
||||
offsetY = offsetY or 0;
|
||||
|
||||
if ( self.buttons ) then
|
||||
buttons = self.buttons;
|
||||
buttonHeight = buttons[1]:GetHeight();
|
||||
else
|
||||
button = CreateFrame("BUTTON", buttonName and (buttonName .. 1) or nil, scrollChild, buttonTemplate);
|
||||
buttonHeight = button:GetHeight();
|
||||
button:SetPoint(initialPoint, scrollChild, initialRelative, initialOffsetX, initialOffsetY);
|
||||
buttons = {}
|
||||
tinsert(buttons, button);
|
||||
end
|
||||
|
||||
self.buttonHeight = round(buttonHeight) - offsetY;
|
||||
|
||||
numButtons = math.ceil(self:GetHeight() / buttonHeight) + 1;
|
||||
|
||||
for i = #buttons + 1, numButtons do
|
||||
button = CreateFrame("BUTTON", buttonName and (buttonName .. i) or nil, scrollChild, buttonTemplate);
|
||||
button:SetPoint(point, buttons[i-1], relativePoint, offsetX, offsetY);
|
||||
tinsert(buttons, button);
|
||||
end
|
||||
|
||||
scrollChild:SetWidth(self:GetWidth())
|
||||
scrollChild:SetHeight(numButtons * buttonHeight);
|
||||
HybridScrollFrame_SetVerticalScrollOffset(self, 0);
|
||||
HybridScrollFrame_UpdateScrollChildRectSafe(self);
|
||||
|
||||
self.buttons = buttons;
|
||||
local scrollBar = HybridScrollFrame_GetScrollBar(self);
|
||||
if scrollBar then
|
||||
scrollBar:SetMinMaxValues(0, numButtons * buttonHeight)
|
||||
scrollBar.buttonHeight = buttonHeight;
|
||||
scrollBar:SetValueStep(.005);
|
||||
scrollBar:SetValue(0);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function HybridScrollFrame_GetButtonIndex(self, button)
|
||||
return tIndexOf(self.buttons, button);
|
||||
end
|
||||
|
||||
function HybridScrollFrame_GetButtons (self)
|
||||
return self.buttons;
|
||||
end
|
||||
|
||||
function HybridScrollFrame_SetDoNotHideScrollBar (self, doNotHide)
|
||||
local scrollBar = HybridScrollFrame_GetScrollBar(self);
|
||||
if not scrollBar or scrollBar.doNotHide == doNotHide then
|
||||
return;
|
||||
end
|
||||
|
||||
scrollBar.doNotHide = doNotHide;
|
||||
HybridScrollFrame_Update(self, self.totalHeight or 0, self.scrollChild:GetHeight());
|
||||
end
|
||||
|
||||
function HybridScrollFrame_ScrollToIndex(self, index, getHeightFunc)
|
||||
local scrollBar = HybridScrollFrame_GetScrollBar(self);
|
||||
if not scrollBar then
|
||||
return;
|
||||
end
|
||||
|
||||
local totalHeight = 0;
|
||||
local scrollFrameHeight = self:GetHeight();
|
||||
for i = 1, index do
|
||||
local entryHeight = getHeightFunc(i);
|
||||
if i == index then
|
||||
local offset = 0;
|
||||
-- we don't need to do anything if the entry is fully displayed with the scroll all the way up
|
||||
if ( totalHeight + entryHeight > scrollFrameHeight ) then
|
||||
if ( entryHeight > scrollFrameHeight ) then
|
||||
-- this entry is larger than the entire scrollframe, put it at the top
|
||||
offset = totalHeight;
|
||||
else
|
||||
-- otherwise place it in the center
|
||||
local diff = scrollFrameHeight - entryHeight;
|
||||
offset = totalHeight - diff / 2;
|
||||
end
|
||||
-- because of valuestep our positioning might change
|
||||
-- we'll do the adjustment ourselves to make sure the entry ends up above the center rather than below
|
||||
local valueStep = scrollBar:GetValueStep();
|
||||
offset = offset + valueStep - mod(offset, valueStep);
|
||||
-- but if we ended up moving the entry so high up that its top is not visible, move it back down
|
||||
if ( offset > totalHeight ) then
|
||||
offset = offset - valueStep;
|
||||
end
|
||||
end
|
||||
scrollBar:SetValue(offset);
|
||||
break;
|
||||
end
|
||||
totalHeight = totalHeight + entryHeight;
|
||||
end
|
||||
end
|
||||
|
||||
function HybridScrollBar_Disable(scrollBar)
|
||||
scrollBar:Disable();
|
||||
local scrollDownButton = scrollBar.ScrollDownButton or _G[scrollBar:GetName().."ScrollDownButton"];
|
||||
if scrollDownButton then
|
||||
scrollDownButton:Disable();
|
||||
end
|
||||
local scrollUpButton = scrollBar.ScrollUpButton or _G[scrollBar:GetName().."ScrollUpButton"];
|
||||
if scrollUpButton then
|
||||
scrollUpButton:Disable();
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,220 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="HybridScrollFrame.lua"/>
|
||||
|
||||
<Texture name="HybridScrollBarButton" virtual="true">
|
||||
<TexCoords left="0.25" right="0.75" top="0.25" bottom="0.75"/>
|
||||
</Texture>
|
||||
|
||||
<Slider name="HybridScrollBarBackgroundTemplate" parentKey="scrollBar" virtual="true">
|
||||
<Size x="20" y="0"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="TOPRIGHT" x="0" y="-18"/>
|
||||
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT" x="0" y="16"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentBG" setAllPoints="true" hidden="true" parentKey="trackBG">
|
||||
<Color r="0" g="0" b="0" a=".85"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="$parentTop" parentKey="ScrollBarTop" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
|
||||
<Size x="27" y="48"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="-4" y="17"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0" right="0.484375" top="0" bottom=".20"/>
|
||||
</Texture>
|
||||
<Texture name="$parentBottom" parentKey="ScrollBarBottom" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
|
||||
<Size x="27" y="64"/>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" x="-4" y="-15"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0.515625" right="1.0" top="0.1440625" bottom="0.4140625"/>
|
||||
</Texture>
|
||||
<Texture name="$parentMiddle" parentKey="ScrollBarMiddle" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentTop" relativePoint="BOTTOMLEFT"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="$parentBottom" relativePoint="TOPRIGHT"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0" right="0.484375" top="0.1640625" bottom="1"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<ThumbTexture name="$parentThumbTexture" file="Interface\Buttons\UI-ScrollBar-Knob" parentKey="thumbTexture">
|
||||
<Size x="18" y="24"/>
|
||||
<TexCoords left="0.20" right="0.80" top="0.125" bottom="0.875"/>
|
||||
</ThumbTexture>
|
||||
</Slider>
|
||||
|
||||
<Slider name="HybridScrollBarTemplate" inherits="HybridScrollBarBackgroundTemplate" virtual="true">
|
||||
<Frames>
|
||||
<Button name="$parentScrollUpButton" parentKey="ScrollUpButton" inherits="UIPanelScrollUpButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM" relativePoint="TOP" x="0" y="-2"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad function="HybridScrollFrameScrollUp_OnLoad"/>
|
||||
<OnClick function ="HybridScrollFrameScrollButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="$parentScrollDownButton" parentKey="ScrollDownButton" inherits="UIPanelScrollDownButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativePoint="BOTTOM" x="0" y="2"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad function="HybridScrollFrameScrollDown_OnLoad"/>
|
||||
<OnClick function="HybridScrollFrameScrollButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnValueChanged function="HybridScrollFrame_OnValueChanged"/>
|
||||
</Scripts>
|
||||
</Slider>
|
||||
|
||||
<Slider name="HybridScrollBarTrimTemplate" parentKey="scrollBar" virtual="true">
|
||||
<Size x="20" y="0"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentBG" setAllPoints="true" hidden="true" parentKey="trackBG">
|
||||
<Color r="0" g="0" b="0" a=".85"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<Texture name="$parentTop" parentKey="Top" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
|
||||
<Size x="24" y="48"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="-4" y="17"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0" right="0.45" top="0" bottom=".20"/>
|
||||
</Texture>
|
||||
<Texture name="$parentBottom" parentKey="Bottom" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
|
||||
<Size x="24" y="64"/>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" x="-4" y="-15"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0.515625" right="0.97" top="0.1440625" bottom="0.4140625"/>
|
||||
</Texture>
|
||||
<Texture name="$parentMiddle" parentKey="Middle" file="Interface\PaperDollInfoFrame\UI-Character-ScrollBar">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentTop" relativePoint="BOTTOMLEFT"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="$parentBottom" relativePoint="TOPRIGHT"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0" right="0.45" top="0.1640625" bottom="1"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button name="$parentScrollUpButton" parentKey="UpButton" inherits="UIPanelScrollUpButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM" relativePoint="TOP" x="0" y="-2"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad function="HybridScrollFrameScrollUp_OnLoad"/>
|
||||
<OnClick function="HybridScrollFrameScrollButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="$parentScrollDownButton" parentKey="DownButton" inherits="UIPanelScrollDownButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativePoint="BOTTOM" x="0" y="2"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad function="HybridScrollFrameScrollDown_OnLoad"/>
|
||||
<OnClick function="HybridScrollFrameScrollButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnValueChanged function="HybridScrollFrame_OnValueChanged"/>
|
||||
</Scripts>
|
||||
<ThumbTexture name="$parentThumbTexture" inherits="HybridScrollBarButton" file="Interface\Buttons\UI-ScrollBar-Knob" parentKey="thumbTexture">
|
||||
<Size x="18" y="24"/>
|
||||
<TexCoords left="0.20" right="0.80" top="0.125" bottom="0.875"/>
|
||||
</ThumbTexture>
|
||||
</Slider>
|
||||
|
||||
<Slider name="MinimalHybridScrollBarTemplate" parentKey="scrollBar" virtual="true">
|
||||
<Size x="22" y="0"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture name="$parentTrack" parentKey="trackBG">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="0" y="17"/>
|
||||
<Anchor point="BOTTOMRIGHT" x="0" y="-17"/>
|
||||
</Anchors>
|
||||
<Color r="0" g="0" b="0" a="1"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button name="$parentScrollUpButton" inherits="UIPanelScrollUpButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" x="0" y="15"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad function="HybridScrollFrameScrollUp_OnLoad"/>
|
||||
<OnClick function="HybridScrollFrameScrollButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="$parentScrollDownButton" inherits="UIPanelScrollDownButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOM" x="0" y="-15"/>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad function="HybridScrollFrameScrollDown_OnLoad"/>
|
||||
<OnClick function="HybridScrollFrameScrollButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnValueChanged function="HybridScrollFrame_OnValueChanged"/>
|
||||
</Scripts>
|
||||
<ThumbTexture name="$parentThumbTexture" inherits="HybridScrollBarButton" file="Interface\Buttons\UI-ScrollBar-Knob" parentKey="thumbTexture">
|
||||
<Size x="18" y="24"/>
|
||||
<TexCoords left="0.20" right="0.80" top="0.125" bottom="0.875"/>
|
||||
</ThumbTexture>
|
||||
</Slider>
|
||||
|
||||
<ScrollFrame name="HybridScrollFrameTemplate" virtual="true">
|
||||
<Scripts>
|
||||
<OnLoad function="HybridScrollFrame_OnLoad"/>
|
||||
<OnMouseWheel function="HybridScrollFrame_OnMouseWheel"/>
|
||||
</Scripts>
|
||||
<ScrollChild>
|
||||
<Frame name="$parentScrollChild" parentKey="ScrollChild">
|
||||
<Scripts>
|
||||
<OnLoad function="HybridScrollFrameScrollChild_OnLoad"/>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
</ScrollChild>
|
||||
</ScrollFrame>
|
||||
|
||||
<ScrollFrame name="BasicHybridScrollFrameTemplate" inherits="HybridScrollFrameTemplate" virtual="true">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"/>
|
||||
<Anchor point="BOTTOMRIGHT"/>
|
||||
</Anchors>
|
||||
<Frames>
|
||||
<Slider name="$parentScrollBar" parentKey="ScrollBar" inherits="HybridScrollBarTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="TOPRIGHT" x="0" y="-17"/>
|
||||
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT" x="0" y="12"/>
|
||||
</Anchors>
|
||||
</Slider>
|
||||
</Frames>
|
||||
</ScrollFrame>
|
||||
|
||||
<!-- This is a scrollframe with no border and a black texture for a track -->
|
||||
<ScrollFrame name="MinimalHybridScrollFrameTemplate" inherits="HybridScrollFrameTemplate" virtual="true">
|
||||
<Frames>
|
||||
<Slider name="$parentScrollBar" inherits="MinimalHybridScrollBarTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="TOPRIGHT" x="0" y="-17"/>
|
||||
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT" x="0" y="17"/>
|
||||
</Anchors>
|
||||
</Slider>
|
||||
</Frames>
|
||||
</ScrollFrame>
|
||||
</Ui>
|
||||
@@ -0,0 +1,355 @@
|
||||
|
||||
NAVBAR_WIDTHBUFFER = 20;
|
||||
|
||||
|
||||
function NavBar_Initialize(self, template, homeData, homeButton, overflowButton)
|
||||
self.template = template;
|
||||
self.freeButtons = {};
|
||||
self.navList = {};
|
||||
self.widthBuffer = NAVBAR_WIDTHBUFFER;
|
||||
|
||||
local name = self:GetName();
|
||||
|
||||
if not self.dropDown then
|
||||
local dropDownName = name and name.."DropDown" or nil;
|
||||
self.dropDown = CreateFrame("Frame", dropDownName, self, "UIDropDownMenuTemplate");
|
||||
UIDropDownMenu_Initialize(self.dropDown, NavBar_DropDown_Initialize, "MENU");
|
||||
end
|
||||
|
||||
if not homeButton then
|
||||
local homeButtonName = name and name.."HomeButton" or nil;
|
||||
homeButton = CreateFrame("BUTTON", homeButtonName, self, self.template);
|
||||
homeButton:SetWidth(homeButton.text:GetStringWidth()+30);
|
||||
end
|
||||
homeButton:SetText(homeData.name or HOME);
|
||||
|
||||
if not overflowButton then
|
||||
local overflowButtonName = name and name.."OverflowButton" or nil;
|
||||
overflowButton = CreateFrame("BUTTON", overflowButtonName, self, self.template);
|
||||
overflowButton:SetWidth(30);
|
||||
|
||||
-- LOOK AT CLICK
|
||||
end
|
||||
|
||||
|
||||
if not homeButton:GetScript("OnEnter") then
|
||||
homeButton:SetScript("OnEnter", NavBar_ButtonOnEnter);
|
||||
end
|
||||
|
||||
if not homeButton:GetScript("OnLeave") then
|
||||
homeButton:SetScript("OnLeave", NavBar_ButtonOnLeave);
|
||||
end
|
||||
|
||||
if ( self.oldStyle ) then
|
||||
homeButton:RegisterForClicks("LeftButtonUp", "RightButtonUp");
|
||||
overflowButton:RegisterForClicks("LeftButtonUp", "RightButtonUp");
|
||||
else
|
||||
homeButton:RegisterForClicks("LeftButtonUp");
|
||||
end
|
||||
overflowButton.listFunc = NavBar_ListOverFlowButtons;
|
||||
homeButton:ClearAllPoints();
|
||||
overflowButton:ClearAllPoints();
|
||||
homeButton:SetPoint("LEFT", self, "LEFT", 0, 0);
|
||||
overflowButton:SetPoint("LEFT", self, "LEFT", 0, 0);
|
||||
overflowButton:Hide();
|
||||
|
||||
homeButton.oldClick = homeButton:GetScript("OnClick");
|
||||
homeButton:SetScript("OnClick", NavBar_ButtonOnClick);
|
||||
overflowButton:SetScript("OnMouseDown", NavBar_ToggleMenu);
|
||||
self.homeButton = homeButton;
|
||||
self.overflowButton = overflowButton;
|
||||
|
||||
|
||||
self.navList[#self.navList+1] = homeButton;
|
||||
homeButton.myclick = homeData.OnClick;
|
||||
homeButton.listFunc = homeData.listFunc;
|
||||
homeButton.data = homeData;
|
||||
homeButton:Show();
|
||||
end
|
||||
|
||||
|
||||
|
||||
function NavBar_Reset(self)
|
||||
for index=2,#self.navList do
|
||||
self.navList[index]:Hide();
|
||||
tinsert(self.freeButtons, self.navList[index])
|
||||
self.navList[index] = nil;
|
||||
end
|
||||
NavBar_CheckLength(self);
|
||||
end
|
||||
|
||||
|
||||
function NavBar_AddButton(self, buttonData)
|
||||
local navButton = self.freeButtons[#self.freeButtons];
|
||||
if navButton then
|
||||
self.freeButtons[#self.freeButtons] = nil;
|
||||
end
|
||||
|
||||
if not navButton then
|
||||
local name = self:GetName();
|
||||
local buttonName = name and name.."Button"..(#self.navList+1);
|
||||
navButton = CreateFrame("BUTTON", buttonName, self, self.template);
|
||||
navButton.oldClick = navButton:GetScript("OnClick");
|
||||
navButton:SetScript("OnClick", NavBar_ButtonOnClick);
|
||||
if ( self.oldStyle ) then
|
||||
navButton:RegisterForClicks("LeftButtonUp", "RightButtonUp");
|
||||
else
|
||||
navButton:RegisterForClicks("LeftButtonUp");
|
||||
end
|
||||
|
||||
if not navButton:GetScript("OnEnter") then
|
||||
navButton:SetScript("OnEnter", NavBar_ButtonOnEnter);
|
||||
end
|
||||
|
||||
if not navButton:GetScript("OnLeave") then
|
||||
navButton:SetScript("OnLeave", NavBar_ButtonOnLeave);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--Set up the button
|
||||
local navParent = self.navList[#self.navList];
|
||||
self.navList[#self.navList+1] = navButton;
|
||||
navButton.navParent = navParent;
|
||||
|
||||
navButton:SetText(buttonData.name);
|
||||
local buttonExtraWidth;
|
||||
navButton.MenuArrowButton:Hide();
|
||||
buttonExtraWidth = 30;
|
||||
navButton.text:SetWidth(self.textMaxWidth or 0);
|
||||
navButton:SetWidth(navButton.text:GetStringWidth() + buttonExtraWidth);
|
||||
navButton.myclick = buttonData.OnClick;
|
||||
navButton.listFunc = buttonData.listFunc;
|
||||
navButton.id = buttonData.id;
|
||||
navButton.data = buttonData;
|
||||
|
||||
navButton:Show();
|
||||
NavBar_CheckLength(self);
|
||||
|
||||
navButton.MenuArrowButton:SetEnabled(not buttonData.isDisabled)
|
||||
end
|
||||
|
||||
function NavBar_ClearTrailingButtons(list, freeList, button)
|
||||
for index=#list,1,-1 do
|
||||
if not list[index] or button == list[index] then
|
||||
break
|
||||
end
|
||||
|
||||
list[index]:Hide();
|
||||
tinsert(freeList, list[index])
|
||||
list[index] = nil;
|
||||
end
|
||||
NavBar_CheckLength(button:GetParent());
|
||||
end
|
||||
|
||||
function NavBar_OpenTo(self, id)
|
||||
local button;
|
||||
local found = false;
|
||||
for i=1, #self.navList do
|
||||
button = self.navList[i];
|
||||
if (button.id and button.id == id) then
|
||||
found = true;
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
if (found) then
|
||||
NavBar_ClearTrailingButtons(self.navList, self.freeButtons, button)
|
||||
end
|
||||
end
|
||||
|
||||
function NavBar_ButtonOnClick(self, button)
|
||||
local parent = self:GetParent()
|
||||
CloseDropDownMenus();
|
||||
if button == "LeftButton" then
|
||||
NavBar_ClearTrailingButtons(parent.navList, parent.freeButtons, self);
|
||||
|
||||
if self.oldClick then
|
||||
self:oldClick(button);
|
||||
end
|
||||
|
||||
if self.myclick then
|
||||
self:myclick(button);
|
||||
end
|
||||
elseif button == "RightButton" then
|
||||
NavBar_ToggleMenu(self);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function NavBar_ButtonOnEnter(self)
|
||||
if self.text:IsTruncated() then
|
||||
GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT");
|
||||
GameTooltip:AddLine(self.text:GetText(), NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, true);
|
||||
GameTooltip:Show();
|
||||
end
|
||||
end
|
||||
|
||||
function NavBar_ButtonOnLeave(self)
|
||||
if self.text:IsTruncated() then
|
||||
GameTooltip:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function NavBar_CheckLength(self)
|
||||
local width = 0;
|
||||
local collapsedWidth;
|
||||
local maxWidth = self:GetWidth() - self.widthBuffer;
|
||||
local xoffset;
|
||||
|
||||
local lastShown;
|
||||
local collapsed = false;
|
||||
|
||||
for i=#self.navList,1,-1 do
|
||||
local currentWidth = width;
|
||||
width = width + self.navList[i]:GetWidth();
|
||||
|
||||
if width > maxWidth then
|
||||
self.navList[i]:Hide();
|
||||
collapsed = true;
|
||||
if not collapsedWidth then -- store the width for adding the offset button
|
||||
collapsedWidth = currentWidth;
|
||||
end
|
||||
else
|
||||
self.navList[i]:Show();
|
||||
if lastShown then
|
||||
local lastButton = self.navList[lastShown];
|
||||
xoffset = self.navList[i].xoffset or 0
|
||||
lastButton:SetPoint("LEFT", self.navList[i], "RIGHT", xoffset, 0);
|
||||
self.navList[i]:SetFrameLevel(lastButton:GetFrameLevel()+1);
|
||||
else
|
||||
self.navList[i]:SetFrameLevel(self:GetFrameLevel()+1);
|
||||
end
|
||||
lastShown = i;
|
||||
end
|
||||
|
||||
if i<#self.navList then
|
||||
if self.navList[i].selected then
|
||||
self.navList[i].selected:Hide();
|
||||
end
|
||||
self.navList[i]:Enable();
|
||||
else
|
||||
if self.navList[i].selected then
|
||||
self.navList[i].selected:Show();
|
||||
end
|
||||
|
||||
self.navList[i]:SetButtonState("NORMAL");
|
||||
self.navList[i]:Disable();
|
||||
end
|
||||
end
|
||||
|
||||
if self.navList[1] then
|
||||
local frameLevel = self.navList[1]:GetFrameLevel()
|
||||
if frameLevel >= self.overlay:GetFrameLevel() then
|
||||
self.overlay:SetFrameLevel(frameLevel + 1)
|
||||
end
|
||||
end
|
||||
|
||||
if collapsed then
|
||||
if collapsedWidth + self.overflowButton:GetWidth() > maxWidth then
|
||||
--No room for the overflow button
|
||||
self.navList[lastShown]:Hide();
|
||||
lastShown = lastShown + 1;
|
||||
end
|
||||
|
||||
self.overflowButton:Show();
|
||||
|
||||
--There should only ever be no lastShown if a single button is longer than maxWidth by itself.
|
||||
if ( lastShown ) then
|
||||
local lastButton = self.navList[lastShown];
|
||||
|
||||
--There should only ever be no lastButton when there is lastShown if a single button is less than maxWidth
|
||||
--but it's width plus the width of the overflow button is longer than maxWidth. In this case the single button
|
||||
--is hidden to make room for the overflowButton.
|
||||
if ( lastButton ) then
|
||||
xoffset = self.overflowButton.xoffset or 0
|
||||
lastButton:SetPoint("LEFT", self.overflowButton, "RIGHT", xoffset, 0);
|
||||
self.overflowButton:SetFrameLevel(lastButton:GetFrameLevel()+1);
|
||||
end
|
||||
end
|
||||
else
|
||||
self.overflowButton:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
function NavBar_ListOverFlowButtons(self, index)
|
||||
local navBar = self:GetParent();
|
||||
|
||||
local button = navBar.navList[index];
|
||||
if not button:IsShown() then
|
||||
return button:GetText(), NavBar_OverflowItemOnClick;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function NavBar_ToggleMenu(self, button)
|
||||
if ( self:GetParent().dropDown.buttonOwner ~= self ) then
|
||||
CloseDropDownMenus();
|
||||
end
|
||||
self:GetParent().dropDown.buttonOwner = self;
|
||||
ToggleDropDownMenu(nil, nil, self:GetParent().dropDown, self, 20, 3);
|
||||
end
|
||||
|
||||
|
||||
function NavBar_DropDown_Initialize(self, level)
|
||||
local navButton = self.buttonOwner;
|
||||
if not navButton or not navButton.listFunc then
|
||||
return;
|
||||
end
|
||||
|
||||
local info = UIDropDownMenu_CreateInfo();
|
||||
info.func = NavBar_DropDown_Click;
|
||||
info.owner = navButton;
|
||||
info.notCheckable = true;
|
||||
local index = 1;
|
||||
local text, func = navButton:listFunc(index);
|
||||
while text do
|
||||
info.text = text;
|
||||
|
||||
info.arg1 = index;
|
||||
info.arg2 = func;
|
||||
UIDropDownMenu_AddButton(info, level);
|
||||
|
||||
index = index + 1;
|
||||
text, func = navButton:listFunc(index);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function NavBar_DropDown_Click(self, index, func)
|
||||
local navButton = self.owner;
|
||||
local navBar = navButton:GetParent();
|
||||
|
||||
if func ~= NavBar_OverflowItemOnClick then
|
||||
NavBar_ClearTrailingButtons(navBar.navList, navBar.freeButtons, navButton);
|
||||
end
|
||||
if type(func) == "function" then
|
||||
func(self, index, navBar);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function NavBar_OverflowItemOnClick(junk, index, navBar)
|
||||
local button = navBar.navList[index];
|
||||
if button then
|
||||
button:Click();
|
||||
end
|
||||
end
|
||||
|
||||
function NavBar_ReconsultationSize( self )
|
||||
self.xoffset = -15
|
||||
local newWidth = min(128, self.text:GetStringWidth()+50)
|
||||
local texCoordoffsetX = (newWidth/128)*0.25
|
||||
|
||||
self:GetNormalTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.70312500, 0.00781250, 0.24218750)
|
||||
self:GetPushedTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.70312500, 0.25781250, 0.49218750)
|
||||
self:GetHighlightTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.71312500, 0.50781250, 0.74218750)
|
||||
|
||||
self:SetWidth(newWidth)
|
||||
end
|
||||
@@ -0,0 +1,241 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="NavigationBar.lua"/>
|
||||
|
||||
<Button name="NavButtonTemplate" motionScriptsWhileDisabled="true" virtual="true">
|
||||
<Size x="80" y="30"/>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture file="Interface\HelpFrame\CS_HelpTextures" parentKey="arrowUp">
|
||||
<Size x="21" y="30"/>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativePoint="RIGHT" x="0" y="0"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0.88867188" right="0.92968750" top="0.29687500" bottom="0.53125000"/>
|
||||
</Texture>
|
||||
<Texture file="Interface\HelpFrame\CS_HelpTextures" parentKey="arrowDown">
|
||||
<Size x="21" y="30"/>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativePoint="RIGHT" x="0" y="0"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0.63281250" right="0.67382813" top="0.75781250" bottom="0.99218750"/>
|
||||
</Texture>
|
||||
<Texture file="Interface\HelpFrame\CS_HelpTextures" parentKey="selected" hidden="true">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="0" y="0"/>
|
||||
<Anchor point="BOTTOMRIGHT" x="0" y="0"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0.00195313" right="0.25195313" top="0.37500000" bottom="0.64062500"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button parentKey="MenuArrowButton" hidden="true">
|
||||
<Size x="27" y="31"/>
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativePoint="TOPRIGHT" x="-2" y="-15"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture parentKey="Art" file="Interface\Buttons\SquareButtonTextures">
|
||||
<Size x="12" y="12"/>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" x="0" y="-1"/>
|
||||
</Anchors>
|
||||
<TexCoords left="0.45312500" right="0.64062500" bottom="0.01562500" top="0.20312500"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<NormalTexture file="Interface\Buttons\UI-SquareButton-Up" parentKey="NormalTexture" alpha="0">
|
||||
<Size x="32" y="32"/>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"/>
|
||||
</Anchors>
|
||||
</NormalTexture>
|
||||
<PushedTexture file="Interface\Buttons\UI-SquareButton-Down" parentKey="PushedTexture" alpha="0">
|
||||
<Size x="32" y="32"/>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"/>
|
||||
</Anchors>
|
||||
</PushedTexture>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD">
|
||||
<Size x="32" y="32"/>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"/>
|
||||
</Anchors>
|
||||
</HighlightTexture>
|
||||
<Scripts>
|
||||
<OnMouseDown>
|
||||
if self:IsEnabled() == 1 then
|
||||
self.Art:SetPoint("CENTER", -1, -2);
|
||||
end
|
||||
</OnMouseDown>
|
||||
<OnMouseUp>
|
||||
if self:IsEnabled() == 1 then
|
||||
self.Art:SetPoint("CENTER", 0, -1);
|
||||
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
|
||||
NavBar_ToggleMenu(self:GetParent());
|
||||
end
|
||||
</OnMouseUp>
|
||||
<OnEnter>
|
||||
self.NormalTexture:SetAlpha(1);
|
||||
self.PushedTexture:SetAlpha(1);
|
||||
</OnEnter>
|
||||
<OnLeave>
|
||||
self.NormalTexture:SetAlpha(0);
|
||||
self.PushedTexture:SetAlpha(0);
|
||||
</OnLeave>
|
||||
<OnEnable>
|
||||
self.NormalTexture:SetDesaturated(false)
|
||||
self.PushedTexture:SetDesaturated(false)
|
||||
self.Art:SetDesaturated(false)
|
||||
</OnEnable>
|
||||
<OnDisable>
|
||||
self.NormalTexture:SetDesaturated(true)
|
||||
self.PushedTexture:SetDesaturated(true)
|
||||
self.Art:SetDesaturated(true)
|
||||
</OnDisable>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
<Scripts>
|
||||
<OnMouseDown>
|
||||
if self:IsEnabled() == 1 then
|
||||
self.arrowUp:Hide();
|
||||
self.arrowDown:Show();
|
||||
end
|
||||
</OnMouseDown>
|
||||
<OnMouseUp>
|
||||
self.arrowDown:Hide();
|
||||
self.arrowUp:Show();
|
||||
</OnMouseUp>
|
||||
<OnClick>
|
||||
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
|
||||
</OnClick>
|
||||
<OnEnable>
|
||||
self.arrowDown:Hide();
|
||||
self.arrowUp:Show();
|
||||
</OnEnable>
|
||||
<OnDisable>
|
||||
self.arrowDown:Hide();
|
||||
self.arrowUp:Show();
|
||||
</OnDisable>
|
||||
</Scripts>
|
||||
<NormalTexture file="Interface\HelpFrame\CS_HelpTextures_Tile" parentKey="NormalTexture" horizTile="true">
|
||||
<TexCoords top="0.06250000" bottom="0.12109375"/>
|
||||
</NormalTexture>
|
||||
<PushedTexture file="Interface\HelpFrame\CS_HelpTextures_Tile" parentKey="PushedTexture" horizTile="true">
|
||||
<TexCoords top="0.12500000" bottom="0.18359375"/>
|
||||
</PushedTexture>
|
||||
<HighlightTexture file="Interface\HelpFrame\CS_HelpTextures" parentKey="HighlightTexture" alphaMode="ADD">
|
||||
<TexCoords left="0.00195313" right="0.25195313" top="0.65625000" bottom="0.92187500"/>
|
||||
</HighlightTexture>
|
||||
<ButtonText name="$parentText" inherits="GameFontNormal" justifyH="LEFT" parentKey="text">
|
||||
<Size x="0" y="12"/>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" x="20" y="0"/>
|
||||
</Anchors>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
|
||||
<Frame name="NavBarTemplate" parentKey="navBar" virtual="true">
|
||||
<Size x="300" y="34"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture file="Interface\HelpFrame\CS_HelpTextures_Tile" horizTile="true">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativePoint="TOPLEFT" x="0" y="0"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT" x="0" y="0"/>
|
||||
</Anchors>
|
||||
<TexCoords top="0.18750000" bottom="0.25390625"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Frame name="$parentOverlay" parentKey="overlay">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="0" y="0"/>
|
||||
<Anchor point="BOTTOMRIGHT" x="0" y="0"/>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture file="Interface\HelpFrame\CS_HelpTextures_Tile" horizTile="true">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="0" y="0"/>
|
||||
<Anchor point="BOTTOMRIGHT" x="0" y="0"/>
|
||||
</Anchors>
|
||||
<TexCoords top="0.25781250" bottom="0.32421875"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
self:SetFrameLevel(50);
|
||||
</OnLoad>
|
||||
</Scripts>
|
||||
</Frame>
|
||||
<Button name="$parentOverflowButton" parentKey="overflow">
|
||||
<Size x="44" y="30"/>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
self.xoffset = -18
|
||||
</OnLoad>
|
||||
<OnClick>
|
||||
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
<NormalTexture file="Interface\HelpFrame\CS_HelpTextures">
|
||||
<TexCoords left="0.54296875" right="0.62890625" top="0.75781250" bottom="0.99218750"/>
|
||||
</NormalTexture>
|
||||
<PushedTexture file="Interface\HelpFrame\CS_HelpTextures">
|
||||
<TexCoords left="0.45312500" right="0.53906250" top="0.75781250" bottom="0.99218750"/>
|
||||
</PushedTexture>
|
||||
<HighlightTexture file="Interface\HelpFrame\CS_HelpTextures" alphaMode="ADD" alpha="0.4">
|
||||
<TexCoords left="0.54296875" right="0.62890625" top="0.75781250" bottom="0.99218750"/>
|
||||
</HighlightTexture>
|
||||
</Button>
|
||||
<Button name="$parentHomeButton" parentKey="home" text="NAVIGATIONBAR_HOME">
|
||||
<Size x="128" y="30"/>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture name="$parentLeft" file="Interface\Common\ShadowOverlay-Left">
|
||||
<Size x="30" y="30"/>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" x="0" y="0"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
self.xoffset = -15;
|
||||
local newWidth = min(128, self.text:GetStringWidth()+50)
|
||||
local texCoordoffsetX = (newWidth/128)*0.25;
|
||||
|
||||
self:GetNormalTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.70312500, 0.00781250, 0.24218750);
|
||||
self:GetPushedTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.70312500, 0.25781250, 0.49218750);
|
||||
self:GetHighlightTexture():SetTexCoord(0.70312500-texCoordoffsetX, 0.71312500, 0.50781250, 0.74218750);
|
||||
|
||||
self:SetWidth(newWidth);
|
||||
</OnLoad>
|
||||
<OnClick>
|
||||
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
<NormalTexture file="Interface\HelpFrame\CS_HelpTextures">
|
||||
</NormalTexture>
|
||||
<PushedTexture file="Interface\HelpFrame\CS_HelpTextures">
|
||||
</PushedTexture>
|
||||
<HighlightTexture file="Interface\HelpFrame\CS_HelpTextures" alphaMode="ADD">
|
||||
</HighlightTexture>
|
||||
<ButtonText name="$parentText" inherits="GameFontNormal" justifyH="LEFT" parentKey="text">
|
||||
<Size x="0" y="12"/>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" x="10" y="0"/>
|
||||
<Anchor point="RIGHT" x="-30" y="0"/>
|
||||
</Anchors>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</Frames>
|
||||
</Frame>
|
||||
</Ui>
|
||||
@@ -0,0 +1,422 @@
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local next = next
|
||||
local CreateFrame = CreateFrame
|
||||
|
||||
ObjectPoolMixin = {};
|
||||
|
||||
function ObjectPoolMixin:OnLoad(creationFunc, resetterFunc)
|
||||
self.creationFunc = creationFunc;
|
||||
self.resetterFunc = resetterFunc;
|
||||
|
||||
self.activeObjects = {};
|
||||
self.inactiveObjects = {};
|
||||
|
||||
self.numActiveObjects = 0;
|
||||
end
|
||||
|
||||
function ObjectPoolMixin:Acquire()
|
||||
local numInactiveObjects = #self.inactiveObjects;
|
||||
if numInactiveObjects > 0 then
|
||||
local obj = self.inactiveObjects[numInactiveObjects];
|
||||
self.activeObjects[obj] = true;
|
||||
self.numActiveObjects = self.numActiveObjects + 1;
|
||||
self.inactiveObjects[numInactiveObjects] = nil;
|
||||
return obj, false;
|
||||
end
|
||||
|
||||
local newObj = self.creationFunc(self);
|
||||
if self.resetterFunc and not self.disallowResetIfNew then
|
||||
self.resetterFunc(self, newObj);
|
||||
end
|
||||
self.activeObjects[newObj] = true;
|
||||
self.numActiveObjects = self.numActiveObjects + 1;
|
||||
return newObj, true;
|
||||
end
|
||||
|
||||
function ObjectPoolMixin:Release(obj)
|
||||
if self:IsActive(obj) then
|
||||
self.inactiveObjects[#self.inactiveObjects + 1] = obj;
|
||||
self.activeObjects[obj] = nil;
|
||||
self.numActiveObjects = self.numActiveObjects - 1;
|
||||
if self.resetterFunc then
|
||||
self.resetterFunc(self, obj);
|
||||
end
|
||||
|
||||
return true;
|
||||
end
|
||||
|
||||
return false;
|
||||
end
|
||||
|
||||
function ObjectPoolMixin:ReleaseAll()
|
||||
for obj in pairs(self.activeObjects) do
|
||||
self:Release(obj);
|
||||
end
|
||||
end
|
||||
|
||||
function ObjectPoolMixin:SetResetDisallowedIfNew(disallowed)
|
||||
self.disallowResetIfNew = disallowed;
|
||||
end
|
||||
|
||||
function ObjectPoolMixin:EnumerateActive()
|
||||
return pairs(self.activeObjects);
|
||||
end
|
||||
|
||||
function ObjectPoolMixin:GetNextActive(current)
|
||||
return (next(self.activeObjects, current));
|
||||
end
|
||||
|
||||
function ObjectPoolMixin:GetNextInactive(current)
|
||||
return (next(self.inactiveObjects, current));
|
||||
end
|
||||
|
||||
function ObjectPoolMixin:IsActive(object)
|
||||
return (self.activeObjects[object] ~= nil);
|
||||
end
|
||||
|
||||
function ObjectPoolMixin:GetNumActive()
|
||||
return self.numActiveObjects;
|
||||
end
|
||||
|
||||
function ObjectPoolMixin:EnumerateInactive()
|
||||
return ipairs(self.inactiveObjects);
|
||||
end
|
||||
|
||||
function CreateObjectPool(creationFunc, resetterFunc)
|
||||
local objectPool = CreateFromMixins(ObjectPoolMixin);
|
||||
objectPool:OnLoad(creationFunc, resetterFunc);
|
||||
return objectPool;
|
||||
end
|
||||
|
||||
FramePoolMixin = CreateFromMixins(ObjectPoolMixin);
|
||||
|
||||
local pollFrameIndex = 0
|
||||
local function FramePoolFactory(framePool)
|
||||
framePool.createdFrames = framePool.createdFrames + 1
|
||||
pollFrameIndex = pollFrameIndex + 1
|
||||
local parentName, frameName = framePool.parent and framePool.parent:GetName()
|
||||
if parentName then
|
||||
frameName = string.format("%sPoolFrame%s%i_%i", parentName, framePool.frameTemplate or "NoTemplate", framePool.createdFrames, pollFrameIndex)
|
||||
elseif framePool.frameTemplate then
|
||||
frameName = string.format("UnnamedPoolFrame%s%i_%i", framePool.frameTemplate, framePool.createdFrames, pollFrameIndex)
|
||||
else
|
||||
frameName = string.format("UnnamedPoolFrameNoTemplate%i_%i", framePool.createdFrames, pollFrameIndex)
|
||||
end
|
||||
return CreateFrame(framePool.frameType, frameName, framePool.parent, framePool.frameTemplate);
|
||||
end
|
||||
|
||||
local function ForbiddenFramePoolFactory(framePool)
|
||||
return FramePoolFactory(framePool)
|
||||
end
|
||||
|
||||
function FramePoolMixin:OnLoad(frameType, parent, frameTemplate, resetterFunc, forbidden, frameInitFunc)
|
||||
if forbidden then
|
||||
local creationFunc = ForbiddenFramePoolFactory;
|
||||
if frameInitFunc ~= nil then
|
||||
creationFunc = function(framePool)
|
||||
local frame = ForbiddenFramePoolFactory(framePool);
|
||||
frameInitFunc(frame);
|
||||
return frame;
|
||||
end
|
||||
end
|
||||
|
||||
ObjectPoolMixin.OnLoad(self, creationFunc, resetterFunc);
|
||||
else
|
||||
local creationFunc = FramePoolFactory;
|
||||
if frameInitFunc ~= nil then
|
||||
creationFunc = function(framePool)
|
||||
local frame = FramePoolFactory(framePool);
|
||||
frameInitFunc(frame);
|
||||
return frame;
|
||||
end
|
||||
end
|
||||
|
||||
ObjectPoolMixin.OnLoad(self, creationFunc, resetterFunc);
|
||||
end
|
||||
self.createdFrames = 0;
|
||||
self.frameType = frameType;
|
||||
self.parent = parent;
|
||||
self.frameTemplate = frameTemplate;
|
||||
end
|
||||
|
||||
function FramePoolMixin:GetTemplate()
|
||||
return self.frameTemplate;
|
||||
end
|
||||
|
||||
function FramePool_Hide(framePool, frame)
|
||||
frame:Hide();
|
||||
end
|
||||
|
||||
function FramePool_HideAndClearAnchors(framePool, frame)
|
||||
frame:Hide();
|
||||
frame:ClearAllPoints();
|
||||
end
|
||||
|
||||
function CreateFramePool(frameType, parent, frameTemplate, resetterFunc, forbidden, frameInitFunc)
|
||||
local framePool = CreateFromMixins(FramePoolMixin);
|
||||
framePool:OnLoad(frameType, parent, frameTemplate, resetterFunc or FramePool_HideAndClearAnchors, forbidden, frameInitFunc);
|
||||
return framePool;
|
||||
end
|
||||
|
||||
TexturePoolMixin = CreateFromMixins(ObjectPoolMixin);
|
||||
|
||||
local function TexturePoolFactory(texturePool)
|
||||
return texturePool.parent:CreateTexture(nil, texturePool.layer, texturePool.textureTemplate, texturePool.subLayer);
|
||||
end
|
||||
|
||||
function TexturePoolMixin:OnLoad(parent, layer, subLayer, textureTemplate, resetterFunc)
|
||||
ObjectPoolMixin.OnLoad(self, TexturePoolFactory, resetterFunc);
|
||||
self.parent = parent;
|
||||
self.layer = layer;
|
||||
self.subLayer = subLayer;
|
||||
self.textureTemplate = textureTemplate;
|
||||
end
|
||||
|
||||
TexturePool_Hide = FramePool_Hide;
|
||||
TexturePool_HideAndClearAnchors = FramePool_HideAndClearAnchors;
|
||||
|
||||
function CreateTexturePool(parent, layer, subLayer, textureTemplate, resetterFunc)
|
||||
local texturePool = CreateFromMixins(TexturePoolMixin);
|
||||
texturePool:OnLoad(parent, layer, subLayer, textureTemplate, resetterFunc or TexturePool_HideAndClearAnchors);
|
||||
return texturePool;
|
||||
end
|
||||
|
||||
FontStringPoolMixin = CreateFromMixins(ObjectPoolMixin);
|
||||
|
||||
local function FontStringPoolFactory(fontStringPool)
|
||||
return fontStringPool.parent:CreateFontString(nil, fontStringPool.layer, fontStringPool.fontStringTemplate, fontStringPool.subLayer);
|
||||
end
|
||||
|
||||
function FontStringPoolMixin:OnLoad(parent, layer, subLayer, fontStringTemplate, resetterFunc)
|
||||
ObjectPoolMixin.OnLoad(self, FontStringPoolFactory, resetterFunc);
|
||||
self.parent = parent;
|
||||
self.layer = layer;
|
||||
self.subLayer = subLayer;
|
||||
self.fontStringTemplate = fontStringTemplate;
|
||||
end
|
||||
|
||||
FontStringPool_Hide = FramePool_Hide;
|
||||
FontStringPool_HideAndClearAnchors = FramePool_HideAndClearAnchors;
|
||||
|
||||
function CreateFontStringPool(parent, layer, subLayer, fontStringTemplate, resetterFunc)
|
||||
local fontStringPool = CreateFromMixins(FontStringPoolMixin);
|
||||
fontStringPool:OnLoad(parent, layer, subLayer, fontStringTemplate, resetterFunc or FontStringPool_HideAndClearAnchors);
|
||||
return fontStringPool;
|
||||
end
|
||||
|
||||
ActorPoolMixin = CreateFromMixins(ObjectPoolMixin);
|
||||
|
||||
local function ActorPoolFactory(actorPool)
|
||||
return actorPool.parent:CreateActor(nil, actorPool.actorTemplate);
|
||||
end
|
||||
|
||||
function ActorPoolMixin:OnLoad(parent, actorTemplate, resetterFunc)
|
||||
ObjectPoolMixin.OnLoad(self, ActorPoolFactory, resetterFunc);
|
||||
self.parent = parent;
|
||||
self.actorTemplate = actorTemplate;
|
||||
end
|
||||
|
||||
ActorPool_Hide = FramePool_Hide;
|
||||
function ActorPool_HideAndClearModel(actorPool, actor)
|
||||
actor:ClearModel();
|
||||
actor:Hide();
|
||||
end
|
||||
|
||||
function CreateActorPool(parent, actorTemplate, resetterFunc)
|
||||
local actorPool = CreateFromMixins(ActorPoolMixin);
|
||||
actorPool:OnLoad(parent, actorTemplate, resetterFunc or ActorPool_HideAndClearModel);
|
||||
return actorPool;
|
||||
end
|
||||
|
||||
FramePoolCollectionMixin = {};
|
||||
|
||||
function CreateFramePoolCollection()
|
||||
local poolCollection = CreateFromMixins(FramePoolCollectionMixin);
|
||||
poolCollection:OnLoad();
|
||||
return poolCollection;
|
||||
end
|
||||
|
||||
function FramePoolCollectionMixin:OnLoad()
|
||||
self.pools = {};
|
||||
end
|
||||
|
||||
function FramePoolCollectionMixin:GetNumActive()
|
||||
local numTotalActive = 0;
|
||||
for _, pool in pairs(self.pools) do
|
||||
numTotalActive = numTotalActive + pool:GetNumActive();
|
||||
end
|
||||
return numTotalActive;
|
||||
end
|
||||
|
||||
-- Returns the pool, and whether or not the pool needed to be created.
|
||||
function FramePoolCollectionMixin:GetOrCreatePool(frameType, parent, template, resetterFunc, forbidden)
|
||||
local pool = self:GetPool(template);
|
||||
if not pool then
|
||||
return self:CreatePool(frameType, parent, template, resetterFunc, forbidden), true;
|
||||
end
|
||||
return pool, false;
|
||||
end
|
||||
|
||||
function FramePoolCollectionMixin:CreatePool(frameType, parent, template, resetterFunc, forbidden, frameInitFunc)
|
||||
assert(self:GetPool(template) == nil);
|
||||
local pool = CreateFramePool(frameType, parent, template, resetterFunc, forbidden, frameInitFunc);
|
||||
self.pools[template] = pool;
|
||||
return pool;
|
||||
end
|
||||
|
||||
function FramePoolCollectionMixin:GetPool(template)
|
||||
return self.pools[template];
|
||||
end
|
||||
|
||||
function FramePoolCollectionMixin:Acquire(template)
|
||||
local pool = self:GetPool(template);
|
||||
assert(pool);
|
||||
return pool:Acquire();
|
||||
end
|
||||
|
||||
function FramePoolCollectionMixin:Release(object)
|
||||
for _, pool in pairs(self.pools) do
|
||||
if pool:Release(object) then
|
||||
-- Found it! Just return
|
||||
return;
|
||||
end
|
||||
end
|
||||
|
||||
-- Huh, we didn't find that object
|
||||
assert(false);
|
||||
end
|
||||
|
||||
function FramePoolCollectionMixin:ReleaseAllByTemplate(template)
|
||||
local pool = self:GetPool(template);
|
||||
if pool then
|
||||
pool:ReleaseAll();
|
||||
end
|
||||
end
|
||||
|
||||
function FramePoolCollectionMixin:ReleaseAll()
|
||||
for key, pool in pairs(self.pools) do
|
||||
pool:ReleaseAll();
|
||||
end
|
||||
end
|
||||
|
||||
function FramePoolCollectionMixin:EnumerateActiveByTemplate(template)
|
||||
local pool = self:GetPool(template);
|
||||
if pool then
|
||||
return pool:EnumerateActive();
|
||||
end
|
||||
|
||||
return nop;
|
||||
end
|
||||
|
||||
function FramePoolCollectionMixin:EnumerateActive()
|
||||
local currentPoolKey, currentPool = next(self.pools, nil);
|
||||
local currentObject = nil;
|
||||
return function()
|
||||
if currentPool then
|
||||
currentObject = currentPool:GetNextActive(currentObject);
|
||||
while not currentObject do
|
||||
currentPoolKey, currentPool = next(self.pools, currentPoolKey);
|
||||
if currentPool then
|
||||
currentObject = currentPool:GetNextActive();
|
||||
else
|
||||
break;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return currentObject;
|
||||
end, nil;
|
||||
end
|
||||
|
||||
function FramePoolCollectionMixin:EnumerateInactiveByTemplate(template)
|
||||
local pool = self:GetPool(template);
|
||||
if pool then
|
||||
return pool:EnumerateInactive();
|
||||
end
|
||||
|
||||
return nop;
|
||||
end
|
||||
|
||||
FixedSizeFramePoolCollectionMixin = CreateFromMixins(FramePoolCollectionMixin);
|
||||
|
||||
function CreateFixedSizeFramePoolCollection()
|
||||
local poolCollection = CreateFromMixins(FixedSizeFramePoolCollectionMixin);
|
||||
poolCollection:OnLoad();
|
||||
return poolCollection;
|
||||
end
|
||||
|
||||
function FixedSizeFramePoolCollectionMixin:OnLoad()
|
||||
FramePoolCollectionMixin.OnLoad(self);
|
||||
self.sizes = {};
|
||||
end
|
||||
|
||||
function FixedSizeFramePoolCollectionMixin:CreatePool(frameType, parent, template, resetterFunc, forbidden, maxPoolSize, preallocate)
|
||||
local pool = FramePoolCollectionMixin.CreatePool(self, frameType, parent, template, resetterFunc, forbidden);
|
||||
|
||||
if preallocate then
|
||||
for i = 1, maxPoolSize do
|
||||
pool:Acquire();
|
||||
end
|
||||
pool:ReleaseAll();
|
||||
end
|
||||
|
||||
self.sizes[template] = maxPoolSize;
|
||||
|
||||
return pool;
|
||||
end
|
||||
|
||||
function FixedSizeFramePoolCollectionMixin:Acquire(template)
|
||||
local pool = self:GetPool(template);
|
||||
assert(pool);
|
||||
|
||||
if pool:GetNumActive() < self.sizes[template] then
|
||||
return pool:Acquire();
|
||||
end
|
||||
return nil;
|
||||
end
|
||||
|
||||
|
||||
FontStringPoolCollectionMixin = CreateFromMixins(FramePoolCollectionMixin);
|
||||
|
||||
function CreateFontStringPoolCollection()
|
||||
local poolCollection = CreateFromMixins(FontStringPoolCollectionMixin);
|
||||
poolCollection:OnLoad();
|
||||
return poolCollection;
|
||||
end
|
||||
|
||||
function FontStringPoolCollectionMixin:GetOrCreatePool(parent, layer, subLayer, fontStringTemplate, resetterFunc)
|
||||
local pool = self:GetPool(fontStringTemplate);
|
||||
if not pool then
|
||||
pool = self:CreatePool(parent, layer, subLayer, fontStringTemplate, resetterFunc);
|
||||
end
|
||||
return pool;
|
||||
end
|
||||
|
||||
function FontStringPoolCollectionMixin:CreatePool(parent, layer, subLayer, fontStringTemplate, resetterFunc)
|
||||
assert(self:GetPool(fontStringTemplate) == nil);
|
||||
local pool = CreateFontStringPool(parent, layer, subLayer, fontStringTemplate, resetterFunc);
|
||||
self.pools[fontStringTemplate] = pool;
|
||||
return pool;
|
||||
end
|
||||
|
||||
function FontStringPoolCollectionMixin:CreatePoolIfNeeded(parent, layer, subLayer, fontStringTemplate, resetterFunc)
|
||||
if not self:GetPool(fontStringTemplate) then
|
||||
self:CreatePool(parent, layer, subLayer, fontStringTemplate, resetterFunc);
|
||||
end
|
||||
end
|
||||
|
||||
function FontStringPoolCollectionMixin:Acquire(fontStringTemplate, parent, layer, subLayer, resetterFunc)
|
||||
local pool = self:GetOrCreatePool(parent, layer, subLayer, fontStringTemplate, resetterFunc);
|
||||
local newString = pool:Acquire();
|
||||
|
||||
if parent then
|
||||
newString:SetParent(parent);
|
||||
end
|
||||
|
||||
if layer then
|
||||
newString:SetDrawLayer(layer, subLayer);
|
||||
end
|
||||
|
||||
return newString;
|
||||
end
|
||||
@@ -0,0 +1,117 @@
|
||||
local SetPortraitTexture = SetPortraitTexture
|
||||
local SetBagPortraitTexture = SetBagPortraitTexture
|
||||
|
||||
TitledPanelMixin = {};
|
||||
|
||||
function TitledPanelMixin:GetTitleText()
|
||||
return self.TitleContainer.TitleText;
|
||||
end
|
||||
|
||||
function TitledPanelMixin:SetTitleColor(color)
|
||||
self:GetTitleText():SetTextColor(color:GetRGBA());
|
||||
end
|
||||
|
||||
function TitledPanelMixin:SetTitle(title)
|
||||
self:GetTitleText():SetText(title);
|
||||
end
|
||||
|
||||
function TitledPanelMixin:SetTitleFormatted(fmt, ...)
|
||||
self:GetTitleText():SetFormattedText(fmt, ...);
|
||||
end
|
||||
|
||||
function TitledPanelMixin:SetTitleMaxLinesAndHeight(maxLines, height)
|
||||
self:GetTitleText():SetMaxLines(maxLines);
|
||||
self:GetTitleText():SetHeight(height);
|
||||
end
|
||||
|
||||
function TitledPanelMixin:SetTitleOffsets(leftOffset, rightOffset)
|
||||
self.TitleContainer:SetPoint("TOPLEFT", self, "TOPLEFT", leftOffset or 58, -1);
|
||||
self.TitleContainer:SetPoint("TOPRIGHT", self, "TOPRIGHT", rightOffset or -24, -1);
|
||||
end
|
||||
|
||||
DefaultPanelMixin = CreateFromMixins(TitledPanelMixin);
|
||||
|
||||
PortraitFrameMixin = CreateFromMixins(TitledPanelMixin);
|
||||
|
||||
function PortraitFrameMixin:GetPortrait()
|
||||
return self.portrait;
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:GetTitleText()
|
||||
return self.TitleText;
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:SetBorder(layoutName)
|
||||
local layout = NineSliceUtil.GetLayout(layoutName);
|
||||
NineSliceUtil.ApplyLayout(self.NineSlice, layout);
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:SetPortraitToAsset(texture)
|
||||
SetPortraitToTexture(self:GetPortrait(), texture);
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:SetPortraitToUnit(unit)
|
||||
SetPortraitTexture(self:GetPortrait(), unit);
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:SetPortraitToBag(bagID)
|
||||
SetBagPortraitTexture(self:GetPortrait(), bagID);
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:SetPortraitTextureRaw(texture)
|
||||
self:GetPortrait():SetTexture(texture);
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:SetPortraitAtlasRaw(atlas, ...)
|
||||
self:GetPortrait():SetAtlas(atlas, ...);
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:SetPortraitToClassIcon(classFilename)
|
||||
self:SetPortraitToAsset("Interface/TargetingFrame/UI-Classes-Circles");
|
||||
local left, right, bottom, top = unpack(CLASS_ICON_TCOORDS[string.upper(classFilename)]);
|
||||
self:SetPortraitTexCoord(left, right, bottom, top);
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:SetPortraitTexCoord(...)
|
||||
self:GetPortrait():SetTexCoord(...);
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:SetPortraitShown(shown)
|
||||
self:GetPortrait():SetShown(shown);
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:SetPortraitTextureSizeAndOffset(size, offsetX, offsetY)
|
||||
local portrait = self:GetPortrait();
|
||||
portrait:SetSize(size, size);
|
||||
portrait:SetPoint("TOPLEFT", self, "TOPLEFT", offsetX, offsetY);
|
||||
end
|
||||
|
||||
do
|
||||
local function SetFrameLevelInternal(frame, level)
|
||||
if frame then
|
||||
-- assertsafe(level < 10000, "Base level needs to be low enough to accomodate the existing layering"); -- TODO: Should become a constant
|
||||
frame:SetFrameLevel(level);
|
||||
end
|
||||
end
|
||||
|
||||
function PortraitFrameMixin:SetFrameLevelsFromBaseLevel(baseLevel)
|
||||
SetFrameLevelInternal(self.NineSlice, baseLevel + 2);
|
||||
SetFrameLevelInternal(self.PortraitContainer, baseLevel + 1);
|
||||
SetFrameLevelInternal(self.TitleContainer, baseLevel + 3);
|
||||
SetFrameLevelInternal(self.CloseButton, baseLevel + 3);
|
||||
end
|
||||
end
|
||||
|
||||
PortraitFrameFlatBaseMixin = {};
|
||||
|
||||
function PortraitFrameFlatBaseMixin:SetBackgroundColor(color)
|
||||
if self.Bg then
|
||||
local bg = self.Bg;
|
||||
color = color or PANEL_BACKGROUND_COLOR;
|
||||
local r, g, b, a = color:GetRGBA();
|
||||
bg.BottomLeft:SetVertexColor(r, g, b, a);
|
||||
bg.BottomRight:SetVertexColor(r, g, b, a);
|
||||
bg.BottomEdge:SetColorTexture(r, g, b, a);
|
||||
bg.TopSection:SetColorTexture(r, g, b, a);
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,660 @@
|
||||
local function getObjTypeMeta(objType)
|
||||
local obj = CreateFrame(objType)
|
||||
obj:Hide()
|
||||
return getmetatable(obj).__index, obj
|
||||
end
|
||||
|
||||
local FrameMeta, FrameObj = getObjTypeMeta("Frame")
|
||||
local FontStringMeta = getmetatable(FrameObj:CreateFontString()).__index
|
||||
local TextureMeta = getmetatable(FrameObj:CreateTexture()).__index
|
||||
|
||||
local ButtonMeta = getObjTypeMeta("Button")
|
||||
local CheckButtonMeta = getObjTypeMeta("CheckButton")
|
||||
local EditBoxMeta = getObjTypeMeta("EditBox")
|
||||
local SimpleHTMLMeta = getObjTypeMeta("SimpleHTML")
|
||||
local SliderMeta = getObjTypeMeta("Slider")
|
||||
local StatusBarMeta = getObjTypeMeta("StatusBar")
|
||||
local ScrollFrameMeta = getObjTypeMeta("ScrollFrame")
|
||||
local MessageFrameMeta = getObjTypeMeta("MessageFrame")
|
||||
local ScrollingMessageFrameMeta = getObjTypeMeta("ScrollingMessageFrame")
|
||||
local ModelMeta = getObjTypeMeta("Model")
|
||||
--local ColorSelectMeta = createAndGetMeta("ColorSelect")
|
||||
--local MovieFrameMeta = createAndGetMeta("MovieFrame")
|
||||
|
||||
local AnimationGroup = FrameObj:CreateAnimationGroup()
|
||||
local AnimationGroupMeta = getmetatable(AnimationGroup).__index
|
||||
local AnimationAnimationMeta = getmetatable(AnimationGroup:CreateAnimation("Animation")).__index
|
||||
local AnimationAlphaMeta = getmetatable(AnimationGroup:CreateAnimation("Alpha")).__index
|
||||
local AnimationRotationMeta = getmetatable(AnimationGroup:CreateAnimation("Rotation")).__index
|
||||
local AnimationPathMeta = getmetatable(AnimationGroup:CreateAnimation("Path")).__index
|
||||
local AnimationScaleMeta = getmetatable(AnimationGroup:CreateAnimation("Scale")).__index
|
||||
local AnimationTranslationMeta = getmetatable(AnimationGroup:CreateAnimation("Translation")).__index
|
||||
|
||||
local GameTooltipMeta, PlayerModelMeta, DressUpModelMeta, TabardModelMeta, ModelFFXMeta
|
||||
|
||||
if IsOnGlueScreen() then
|
||||
ModelFFXMeta = getObjTypeMeta("ModelFFX")
|
||||
else
|
||||
GameTooltipMeta = getObjTypeMeta("GameTooltip")
|
||||
PlayerModelMeta = getObjTypeMeta("PlayerModel")
|
||||
DressUpModelMeta = getObjTypeMeta("DressUpModel")
|
||||
TabardModelMeta = getObjTypeMeta("TabardModel")
|
||||
|
||||
-- local CooldownMeta = createAndGetMeta("Cooldown")
|
||||
-- local MinimapMeta = createAndGetMeta("Minimap")
|
||||
-- local TaxiRouteFrameMeta = createAndGetMeta("TaxiRouteFrame")
|
||||
-- local QuestPOIFrameMeta = createAndGetMeta("QuestPOIFrame")
|
||||
-- local WorldFrameMeta = createAndGetMeta("WorldFrame")
|
||||
end
|
||||
|
||||
local function nop() end
|
||||
|
||||
local function ScriptRegion_SetShown(self, show)
|
||||
if show then
|
||||
self:Show()
|
||||
else
|
||||
self:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
local function ScriptRegion_GetScaledRect(self)
|
||||
local left, bottom, width, height = self:GetRect()
|
||||
if left and bottom and width and height then
|
||||
local scale = self:GetEffectiveScale()
|
||||
return left * scale, bottom * scale, width * scale, height * scale
|
||||
end
|
||||
end
|
||||
|
||||
local function ScriptRegion_IsMouseOverEx(self, ignoreVisibility, checkMouseFocus)
|
||||
if ignoreVisibility or self:IsVisible() then
|
||||
local l, r, t, b = self:GetHitRectInsets()
|
||||
return self:IsMouseOver(-t, b, l, -r) and (not checkMouseFocus or self == GetMouseFocus())
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function ScriptRegionResizing_ClearAndSetPoint(self, ...)
|
||||
self:ClearAllPoints()
|
||||
self:SetPoint(...)
|
||||
end
|
||||
|
||||
local function Region_GetEffectiveScale(self)
|
||||
return self:GetParent():GetEffectiveScale()
|
||||
end
|
||||
|
||||
local function LayoutFrame_SetParentArray(self, arrayName, element, setInSelf)
|
||||
local parent = not setInSelf and self:GetParent() or self
|
||||
|
||||
if not parent[arrayName] then
|
||||
parent[arrayName] = {}
|
||||
end
|
||||
|
||||
table.insert(parent[arrayName], element or self)
|
||||
end
|
||||
|
||||
local RegisterCustomEvent = RegisterCustomEvent
|
||||
local UnregisterCustomEvent = UnregisterCustomEvent
|
||||
|
||||
local function Frame_RegisterCustomEvent(self, event)
|
||||
RegisterCustomEvent(self, event)
|
||||
end
|
||||
|
||||
local function Frame_UnregisterCustomEvent(self, event)
|
||||
UnregisterCustomEvent(self, event)
|
||||
end
|
||||
|
||||
local function FontString_SetDesaturated(self, toggle, color)
|
||||
if toggle then
|
||||
self:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b)
|
||||
else
|
||||
if type(color) == "table" then
|
||||
self:SetTextColor(color.r, color.g, color.b)
|
||||
else
|
||||
self:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local SECONDS_PER_DAY = 86400
|
||||
local function FontString_SetRemainingTime(self, seconds, daysformat)
|
||||
if type(seconds) ~= "number" then
|
||||
self:SetText("")
|
||||
return
|
||||
end
|
||||
|
||||
if daysformat then
|
||||
if seconds >= SECONDS_PER_DAY then
|
||||
self:SetFormattedText(D_DAYS_FULL, math.floor(seconds / SECONDS_PER_DAY))
|
||||
else
|
||||
self:SetText(date("!%X", seconds))
|
||||
end
|
||||
elseif seconds >= SECONDS_PER_DAY then
|
||||
local days = string.format(DAY_ONELETTER_ABBR_SHORT, math.floor(seconds / SECONDS_PER_DAY))
|
||||
self:SetFormattedText("%s %s", days, date("!%X", seconds % SECONDS_PER_DAY))
|
||||
elseif seconds >= 0 then
|
||||
self:SetText(date("!%X", seconds))
|
||||
else
|
||||
self:SetText("")
|
||||
end
|
||||
end
|
||||
|
||||
local function Texture_SetSubTexCoord(self, left, right, top, bottom)
|
||||
local ULx, ULy, LLx, LLy, URx, URy, LRx, LRy = self:GetTexCoord()
|
||||
|
||||
local leftedge = ULx
|
||||
local rightedge = URx
|
||||
local topedge = ULy
|
||||
local bottomedge = LLy
|
||||
|
||||
local width = rightedge - leftedge
|
||||
local height = bottomedge - topedge
|
||||
|
||||
leftedge = ULx + width * left
|
||||
topedge = ULy + height * top
|
||||
rightedge = math.max(rightedge * right, ULx)
|
||||
bottomedge = math.max(bottomedge * bottom, ULy)
|
||||
|
||||
ULx = leftedge
|
||||
ULy = topedge
|
||||
LLx = leftedge
|
||||
LLy = bottomedge
|
||||
URx = rightedge
|
||||
URy = topedge
|
||||
LRx = rightedge
|
||||
LRy = bottomedge
|
||||
|
||||
self:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy)
|
||||
end
|
||||
|
||||
local ATLAS_FIELD_WIDTH = 1
|
||||
local ATLAS_FIELD_HEIGHT = 2
|
||||
local ATLAS_FIELD_LEFT = 3
|
||||
local ATLAS_FIELD_RIGHT = 4
|
||||
local ATLAS_FIELD_TOP = 5
|
||||
local ATLAS_FIELD_BOTTOM = 6
|
||||
local ATLAS_FIELD_TILESHORIZ = 7
|
||||
local ATLAS_FIELD_TILESVERT = 8
|
||||
local ATLAS_FIELD_TEXTUREPATH = 9
|
||||
|
||||
local S_ATLAS_STORAGE = S_ATLAS_STORAGE
|
||||
|
||||
local function Texture_SetAtlas(self, atlasName, useAtlasSize, filterMode)
|
||||
if type(self) ~= "table" then
|
||||
error("Attempt to find 'this' in non-table object (used '.' instead of ':' ?)", 3)
|
||||
elseif type(atlasName) ~= "string" then
|
||||
error(string.format("Usage: %s:SetAtlas(\"atlasName\"[, useAtlasSize])", self:GetName() or tostring(self)), 3)
|
||||
end
|
||||
|
||||
local atlas = S_ATLAS_STORAGE[atlasName]
|
||||
if not atlas then
|
||||
error(string.format("%s:SetAtlas: Atlas %s does not exist", self:GetName() or tostring(self), atlasName), 3)
|
||||
end
|
||||
|
||||
local success = self:SetTexture(atlas[ATLAS_FIELD_TEXTUREPATH] or "", atlas[ATLAS_FIELD_TILESHORIZ], atlas[ATLAS_FIELD_TILESVERT])
|
||||
if success then
|
||||
if useAtlasSize then
|
||||
self:SetWidth(atlas[ATLAS_FIELD_WIDTH])
|
||||
self:SetHeight(atlas[ATLAS_FIELD_HEIGHT])
|
||||
end
|
||||
|
||||
self:SetTexCoord(atlas[ATLAS_FIELD_LEFT], atlas[ATLAS_FIELD_RIGHT], atlas[ATLAS_FIELD_TOP], atlas[ATLAS_FIELD_BOTTOM])
|
||||
|
||||
self:SetHorizTile(atlas[ATLAS_FIELD_TILESHORIZ])
|
||||
self:SetVertTile(atlas[ATLAS_FIELD_TILESVERT])
|
||||
end
|
||||
|
||||
return success
|
||||
end
|
||||
|
||||
local function Texture_SetPortrait(self, displayID)
|
||||
local portrait = strconcat("Interface\\PORTRAITS\\Portrait_model_", tonumber(displayID))
|
||||
local success = self:SetTexture(portrait)
|
||||
return success
|
||||
end
|
||||
|
||||
local function Button_SetEnabled(self, enabled)
|
||||
if enabled then
|
||||
self:Enable()
|
||||
else
|
||||
self:Disable()
|
||||
end
|
||||
end
|
||||
|
||||
local function Button_SetNormalAtlas(self, atlasName, useAtlasSize, filterMode)
|
||||
local texture = self:GetNormalTexture()
|
||||
if not texture then
|
||||
self:SetNormalTexture("")
|
||||
texture = self:GetNormalTexture()
|
||||
end
|
||||
Texture_SetAtlas(texture, atlasName, useAtlasSize, filterMode)
|
||||
end
|
||||
|
||||
local function Button_SetPushedAtlas(self, atlasName, useAtlasSize, filterMode)
|
||||
local texture = self:GetPushedTexture()
|
||||
if not texture then
|
||||
self:SetPushedTexture("")
|
||||
texture = self:GetPushedTexture()
|
||||
end
|
||||
Texture_SetAtlas(texture, atlasName, useAtlasSize, filterMode)
|
||||
end
|
||||
|
||||
local function Button_SetDisabledAtlas(self, atlasName, useAtlasSize, filterMode)
|
||||
local texture = self:GetDisabledTexture()
|
||||
if not texture then
|
||||
self:SetDisabledTexture("")
|
||||
texture = self:GetDisabledTexture()
|
||||
end
|
||||
Texture_SetAtlas(texture, atlasName, useAtlasSize, filterMode)
|
||||
end
|
||||
|
||||
local function Button_SetHighlightAtlas(self, atlasName, useAtlasSize, filterMode)
|
||||
local texture = self:GetHighlightTexture()
|
||||
if not texture then
|
||||
self:SetHighlightTexture("")
|
||||
texture = self:GetHighlightTexture()
|
||||
end
|
||||
Texture_SetAtlas(texture, atlasName, useAtlasSize, filterMode)
|
||||
end
|
||||
|
||||
local function Button_ClearNormalTexture(self)
|
||||
self:SetNormalTexture("")
|
||||
end
|
||||
|
||||
local function Button_ClearPushedTexture(self)
|
||||
self:SetPushedTexture("")
|
||||
end
|
||||
|
||||
local function Button_ClearDisabledTexture(self)
|
||||
self:SetDisabledTexture("")
|
||||
end
|
||||
|
||||
local function Button_ClearHighlightTexture(self)
|
||||
self:SetHighlightTexture("")
|
||||
end
|
||||
|
||||
local function CheckButton_SetCheckedAtlas(self, atlasName, useAtlasSize, filterMode)
|
||||
local texture = self:GetCheckedTexture()
|
||||
if not texture then
|
||||
self:SetCheckedTexture("")
|
||||
texture = self:GetCheckedTexture()
|
||||
end
|
||||
Texture_SetAtlas(texture, atlasName, useAtlasSize, filterMode)
|
||||
end
|
||||
|
||||
local function CheckButton_SetDisabledCheckedAtlas(self, atlasName, useAtlasSize, filterMode)
|
||||
local texture = self:GetDisabledCheckedTexture()
|
||||
if not texture then
|
||||
self:SetDisabledCheckedTexture("")
|
||||
texture = self:GetDisabledCheckedTexture()
|
||||
end
|
||||
Texture_SetAtlas(texture, atlasName, useAtlasSize, filterMode)
|
||||
end
|
||||
|
||||
local function CheckButton_ClearCheckedTexture(self)
|
||||
self:SetCheckedTexture("")
|
||||
end
|
||||
|
||||
local function CheckButton_ClearDisabledCheckedTexture(self)
|
||||
self:SetDisabledCheckedTexture("")
|
||||
end
|
||||
|
||||
local function EditBox_IsEnabled(self)
|
||||
return self.__ext_Disabled ~= true
|
||||
end
|
||||
|
||||
local function EditBox_Enabled(self)
|
||||
if self.__ext_mouseEnabled then
|
||||
self:EnableMouse(true)
|
||||
end
|
||||
if self.__ext_autoFocus then
|
||||
self:SetAutoFocus(true)
|
||||
self:SetFocus()
|
||||
self:HighlightText(0, 0)
|
||||
end
|
||||
self.SetFocus = nil
|
||||
self.__ext_Disabled = nil
|
||||
self.__ext_autoFocus = nil
|
||||
self.__ext_mouseEnabled = nil
|
||||
end
|
||||
|
||||
local function EditBox_Disable(self)
|
||||
if not self.__ext_Disabled then
|
||||
self.__ext_autoFocus = self:IsAutoFocus() == 1
|
||||
self.__ext_mouseEnabled = self:IsMouseEnabled() == 1
|
||||
end
|
||||
self:SetAutoFocus(false)
|
||||
self:ClearFocus()
|
||||
self:EnableMouse(false)
|
||||
self.SetFocus = nop
|
||||
self.__ext_Disabled = true
|
||||
end
|
||||
|
||||
local function PlayerModel_UpdateModelEffects(self)
|
||||
self:ResetAllSpells()
|
||||
|
||||
do -- 3D armor
|
||||
local displayIDs = self:GetDisplayIDs()
|
||||
if displayIDs then
|
||||
for index, displayID in ipairs(displayIDs) do
|
||||
if displayID ~= 0 then
|
||||
local spellID = ITEMS_WITH_3D[displayID]
|
||||
if spellID then
|
||||
self:SetSpell(spellID)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do -- customizations
|
||||
if not self.__hideCustomizations then
|
||||
local guid = self.__unitGUID
|
||||
if guid and guid == UnitGUID("player") then
|
||||
local activeCustomizationItems = C_CustomizationsCollection.GetActiveCustomizationItems(1)
|
||||
if activeCustomizationItems then
|
||||
for index, customizationItemID in ipairs(activeCustomizationItems) do
|
||||
local spellID = C_CustomizationsCollection.GetCustomizationSpellByItem(customizationItemID)
|
||||
if spellID then
|
||||
if spellID == 320799 then
|
||||
local SPELL_EFFECTS_GIFT_OF_MOST_PRECIOUS = {313589, 313590, 313591, 313592, 313593, 313594}
|
||||
spellID = SPELL_EFFECTS_GIFT_OF_MOST_PRECIOUS[math.random(1, #SPELL_EFFECTS_GIFT_OF_MOST_PRECIOUS)]
|
||||
end
|
||||
self:SetSpell(spellID)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function PlayerModel_SetUnit(self, unit, isCustomPosition, positionData)
|
||||
if type(unit) ~= "string" then
|
||||
error("Usage: SetUnit(\"unit\")", 2)
|
||||
end
|
||||
|
||||
local objectType = self:GetObjectType()
|
||||
if objectType == "PlayerModel" then
|
||||
self:SetUnitRaw(unit)
|
||||
return
|
||||
end
|
||||
|
||||
if isCustomPosition then
|
||||
if objectType == "TabardModel" then
|
||||
self:SetPosition(0, 0, 0)
|
||||
else
|
||||
self:SetPosition(1, 1, 1)
|
||||
end
|
||||
end
|
||||
|
||||
self:SetUnitRaw(unit)
|
||||
|
||||
self.__hideCustomizations = nil
|
||||
self.__unitGUID = UnitGUID(unit)
|
||||
|
||||
PlayerModel_UpdateModelEffects(self)
|
||||
|
||||
if isCustomPosition then
|
||||
local unitSex = UnitSex(unit) or 2
|
||||
local _, unitRace = UnitRace(unit)
|
||||
local positionStorage = positionData or (objectType == "TabardModel" and TABARDMODEL_CAMERA_POSITION or DRESSUPMODEL_CAMERA_POSITION)
|
||||
local data = positionStorage[string.format("%s%d", unitRace or "Human", unitSex)]
|
||||
|
||||
if data then
|
||||
local positionX, positionY, positionZ, facing = unpack(data, 1, 4)
|
||||
self.basePositionOverrideX = positionX
|
||||
self.basePositionOverrideY = positionY
|
||||
self.basePositionOverrideZ = positionZ
|
||||
self.basePositionOverrideXasZ = true
|
||||
self:SetPosition(positionX or 0, positionY or 0, positionZ or 0)
|
||||
|
||||
if facing then
|
||||
self:SetFacing(math.rad(facing))
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self.basePositionOverrideX = nil
|
||||
self.basePositionOverrideY = nil
|
||||
self.basePositionOverrideZ = nil
|
||||
self.basePositionOverrideXasZ = nil
|
||||
end
|
||||
|
||||
local function PlayerModel_RefreshUnit(self)
|
||||
self:RefreshUnitRaw()
|
||||
|
||||
local objectType = self:GetObjectType()
|
||||
if objectType == "DressUpModel" or objectType == "TabardModel" then
|
||||
self.__hideCustomizations = nil
|
||||
PlayerModel_UpdateModelEffects(self)
|
||||
end
|
||||
end
|
||||
|
||||
local function DressUpModel_Dress(self)
|
||||
self:DressRaw()
|
||||
self.__hideCustomizations = nil
|
||||
PlayerModel_UpdateModelEffects(self)
|
||||
end
|
||||
|
||||
local function DressUpModel_Undress(self)
|
||||
self:UndressRaw()
|
||||
self.__hideCustomizations = true
|
||||
PlayerModel_UpdateModelEffects(self)
|
||||
end
|
||||
|
||||
local function DressUpModel_TryOn(self, item)
|
||||
if C_CustomizationsCollection.CanPreviewCustomizationInDressUp(item) then
|
||||
local spellID, category, subcategory = C_CustomizationsCollection.GetCustomizationSpellByItem(item)
|
||||
if spellID then
|
||||
PlayerModel_UpdateModelEffects(self)
|
||||
|
||||
for index, activeSpellID in ipairs({self:GetAllSpells()}) do
|
||||
local isCustomizationSpell, activeSpellCategory, activeSpellSubCategory = C_CustomizationsCollection.IsCustomizationSpell(activeSpellID)
|
||||
if isCustomizationSpell and category == activeSpellCategory and subcategory == activeSpellSubCategory then
|
||||
self:ResetSpell(activeSpellID)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if spellID == 320799 then
|
||||
local SPELL_EFFECTS_GIFT_OF_MOST_PRECIOUS = {313589, 313590, 313591, 313592, 313593, 313594}
|
||||
spellID = SPELL_EFFECTS_GIFT_OF_MOST_PRECIOUS[math.random(1, #SPELL_EFFECTS_GIFT_OF_MOST_PRECIOUS)]
|
||||
end
|
||||
|
||||
self:SetSpell(spellID)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
self:TryOnRaw(item)
|
||||
PlayerModel_UpdateModelEffects(self)
|
||||
end
|
||||
|
||||
local function Animation_Restart(self)
|
||||
if self:IsPlaying() then
|
||||
self:Stop()
|
||||
end
|
||||
self:Play()
|
||||
end
|
||||
|
||||
FrameMeta.SetShown = ScriptRegion_SetShown
|
||||
FrameMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
FrameMeta.IsMouseOverEx = ScriptRegion_IsMouseOverEx
|
||||
FrameMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
FrameMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
FrameMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
FrameMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
|
||||
FontStringMeta.SetShown = ScriptRegion_SetShown
|
||||
FontStringMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
FontStringMeta.GetEffectiveScale = Region_GetEffectiveScale
|
||||
FontStringMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
FontStringMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
FontStringMeta.SetDesaturated = FontString_SetDesaturated
|
||||
FontStringMeta.SetRemainingTime = FontString_SetRemainingTime
|
||||
|
||||
TextureMeta.SetShown = ScriptRegion_SetShown
|
||||
TextureMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
TextureMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
TextureMeta.GetEffectiveScale = Region_GetEffectiveScale
|
||||
TextureMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
TextureMeta.SetSubTexCoord = Texture_SetSubTexCoord
|
||||
TextureMeta.SetAtlas = Texture_SetAtlas
|
||||
TextureMeta.SetPortrait = Texture_SetPortrait
|
||||
|
||||
ButtonMeta.SetShown = ScriptRegion_SetShown
|
||||
ButtonMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
ButtonMeta.IsMouseOverEx = ScriptRegion_IsMouseOverEx
|
||||
ButtonMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
ButtonMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
ButtonMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
ButtonMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
ButtonMeta.SetEnabled = Button_SetEnabled
|
||||
ButtonMeta.SetNormalAtlas = Button_SetNormalAtlas
|
||||
ButtonMeta.SetPushedAtlas = Button_SetPushedAtlas
|
||||
ButtonMeta.SetDisabledAtlas = Button_SetDisabledAtlas
|
||||
ButtonMeta.SetHighlightAtlas = Button_SetHighlightAtlas
|
||||
ButtonMeta.ClearClearNormalTexture = Button_ClearNormalTexture
|
||||
ButtonMeta.ClearPushedTexture = Button_ClearPushedTexture
|
||||
ButtonMeta.ClearDisabledTexture = Button_ClearDisabledTexture
|
||||
ButtonMeta.ClearHighlightTexture = Button_ClearHighlightTexture
|
||||
|
||||
CheckButtonMeta.SetShown = ScriptRegion_SetShown
|
||||
CheckButtonMeta.IsMouseOverEx = ScriptRegion_IsMouseOverEx
|
||||
CheckButtonMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
CheckButtonMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
CheckButtonMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
CheckButtonMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
CheckButtonMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
CheckButtonMeta.SetEnabled = Button_SetEnabled
|
||||
CheckButtonMeta.SetNormalAtlas = Button_SetNormalAtlas
|
||||
CheckButtonMeta.SetPushedAtlas = Button_SetPushedAtlas
|
||||
CheckButtonMeta.SetDisabledAtlas = Button_SetDisabledAtlas
|
||||
CheckButtonMeta.SetHighlightAtlas = Button_SetHighlightAtlas
|
||||
CheckButtonMeta.SetCheckedAtlas = CheckButton_SetCheckedAtlas
|
||||
CheckButtonMeta.SetDisabledCheckedAtlas = CheckButton_SetDisabledCheckedAtlas
|
||||
CheckButtonMeta.ClearClearNormalTexture = Button_ClearNormalTexture
|
||||
CheckButtonMeta.ClearPushedTexture = Button_ClearPushedTexture
|
||||
CheckButtonMeta.ClearDisabledTexture = Button_ClearDisabledTexture
|
||||
CheckButtonMeta.ClearHighlightTexture = Button_ClearHighlightTexture
|
||||
CheckButtonMeta.ClearDisabledCheckedTexture = CheckButton_ClearCheckedTexture
|
||||
CheckButtonMeta.ClearDisabledCheckedTexture = CheckButton_ClearDisabledCheckedTexture
|
||||
|
||||
EditBoxMeta.SetShown = ScriptRegion_SetShown
|
||||
EditBoxMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
EditBoxMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
EditBoxMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
EditBoxMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
EditBoxMeta.SetEnabled = Button_SetEnabled
|
||||
EditBoxMeta.IsEnabled = EditBox_IsEnabled
|
||||
EditBoxMeta.Enable = EditBox_Enabled
|
||||
EditBoxMeta.Disable = EditBox_Disable
|
||||
|
||||
SimpleHTMLMeta.SetShown = ScriptRegion_SetShown
|
||||
SimpleHTMLMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
SimpleHTMLMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
SimpleHTMLMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
SimpleHTMLMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
SimpleHTMLMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
|
||||
SliderMeta.SetShown = ScriptRegion_SetShown
|
||||
SliderMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
SliderMeta.IsMouseOverEx = ScriptRegion_IsMouseOverEx
|
||||
SliderMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
SliderMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
SliderMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
SliderMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
SliderMeta.SetEnabled = Button_SetEnabled
|
||||
|
||||
StatusBarMeta.SetShown = ScriptRegion_SetShown
|
||||
StatusBarMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
StatusBarMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
StatusBarMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
StatusBarMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
StatusBarMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
|
||||
ScrollFrameMeta.SetShown = ScriptRegion_SetShown
|
||||
ScrollFrameMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
ScrollFrameMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
ScrollFrameMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
ScrollFrameMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
ScrollFrameMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
|
||||
MessageFrameMeta.SetShown = ScriptRegion_SetShown
|
||||
MessageFrameMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
MessageFrameMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
MessageFrameMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
MessageFrameMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
MessageFrameMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
|
||||
ScrollingMessageFrameMeta.SetShown = ScriptRegion_SetShown
|
||||
ScrollingMessageFrameMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
ScrollingMessageFrameMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
ScrollingMessageFrameMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
ScrollingMessageFrameMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
ScrollingMessageFrameMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
|
||||
ModelMeta.SetShown = ScriptRegion_SetShown
|
||||
ModelMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
ModelMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
ModelMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
ModelMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
|
||||
AnimationGroupMeta.Restart = Animation_Restart
|
||||
AnimationAnimationMeta.Restart = Animation_Restart
|
||||
AnimationAlphaMeta.Restart = Animation_Restart
|
||||
AnimationRotationMeta.Restart = Animation_Restart
|
||||
AnimationPathMeta.Restart = Animation_Restart
|
||||
AnimationScaleMeta.Restart = Animation_Restart
|
||||
AnimationTranslationMeta.Restart = Animation_Restart
|
||||
|
||||
if IsOnGlueScreen() then
|
||||
ModelFFXMeta.SetShown = ScriptRegion_SetShown
|
||||
ModelFFXMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
ModelFFXMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
else
|
||||
GameTooltipMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
GameTooltipMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
GameTooltipMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
|
||||
PlayerModelMeta.SetShown = ScriptRegion_SetShown
|
||||
PlayerModelMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
PlayerModelMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
PlayerModelMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
PlayerModelMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
PlayerModelMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
PlayerModelMeta.SetUnitRaw = PlayerModelMeta.SetUnit
|
||||
PlayerModelMeta.SetUnit = PlayerModel_SetUnit
|
||||
PlayerModelMeta.RefreshUnitRaw = PlayerModelMeta.RefreshUnit
|
||||
PlayerModelMeta.RefreshUnit = PlayerModel_RefreshUnit
|
||||
|
||||
DressUpModelMeta.SetShown = ScriptRegion_SetShown
|
||||
DressUpModelMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
DressUpModelMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
DressUpModelMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
DressUpModelMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
DressUpModelMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
DressUpModelMeta.SetUnitRaw = DressUpModelMeta.SetUnit
|
||||
DressUpModelMeta.SetUnit = PlayerModel_SetUnit
|
||||
DressUpModelMeta.RefreshUnitRaw = DressUpModelMeta.RefreshUnit
|
||||
DressUpModelMeta.RefreshUnit = PlayerModel_RefreshUnit
|
||||
DressUpModelMeta.TryOnRaw = DressUpModelMeta.TryOn
|
||||
DressUpModelMeta.TryOn = DressUpModel_TryOn
|
||||
DressUpModelMeta.DressRaw = DressUpModelMeta.Dress
|
||||
DressUpModelMeta.Dress = DressUpModel_Dress
|
||||
DressUpModelMeta.UndressRaw = DressUpModelMeta.Undress
|
||||
DressUpModelMeta.Undress = DressUpModel_Undress
|
||||
|
||||
TabardModelMeta.SetShown = ScriptRegion_SetShown
|
||||
TabardModelMeta.GetScaledRect = ScriptRegion_GetScaledRect
|
||||
TabardModelMeta.ClearAndSetPoint = ScriptRegionResizing_ClearAndSetPoint
|
||||
TabardModelMeta.SetParentArray = LayoutFrame_SetParentArray
|
||||
TabardModelMeta.RegisterCustomEvent = Frame_RegisterCustomEvent
|
||||
TabardModelMeta.UnregisterCustomEvent = Frame_UnregisterCustomEvent
|
||||
TabardModelMeta.SetUnitRaw = TabardModelMeta.SetUnit
|
||||
TabardModelMeta.SetUnit = PlayerModel_SetUnit
|
||||
TabardModelMeta.RefreshUnitRaw = TabardModelMeta.RefreshUnit
|
||||
TabardModelMeta.RefreshUnit = PlayerModel_RefreshUnit
|
||||
TabardModelMeta.SetSpell = function() end
|
||||
end
|
||||
@@ -0,0 +1,196 @@
|
||||
local function SetupTextFont(fontString, fontObject)
|
||||
if fontString and fontObject then
|
||||
fontString:SetFontObject(fontObject);
|
||||
end
|
||||
end
|
||||
|
||||
function SharedTooltip_OnLoad(self)
|
||||
local style = nil;
|
||||
local isEmbedded = false;
|
||||
SharedTooltip_SetBackdropStyle(self, style, isEmbedded);
|
||||
self:SetClampRectInsets(0, 0, 15, 0);
|
||||
|
||||
SetupTextFont(self.TextLeft1, self.textLeft1Font);
|
||||
SetupTextFont(self.TextRight1, self.textRight1Font);
|
||||
SetupTextFont(self.TextLeft2, self.textLeft2Font);
|
||||
SetupTextFont(self.TextRight2, self.textRight2Font);
|
||||
end
|
||||
|
||||
function SharedTooltip_OnHide(self)
|
||||
self:SetPadding(0, 0, 0, 0);
|
||||
end
|
||||
|
||||
local DEFAULT_TOOLTIP_OFFSET_X = -17;
|
||||
local DEFAULT_TOOLTIP_OFFSET_Y = 70;
|
||||
|
||||
function SharedTooltip_SetDefaultAnchor(tooltip, parent)
|
||||
tooltip:SetOwner(parent or GetAppropriateTopLevelParent(), "ANCHOR_NONE");
|
||||
tooltip:SetPoint("BOTTOMRIGHT", GetAppropriateTopLevelParent(), "BOTTOMRIGHT", DEFAULT_TOOLTIP_OFFSET_X, DEFAULT_TOOLTIP_OFFSET_Y);
|
||||
end
|
||||
|
||||
function SharedTooltip_ClearInsertedFrames(self)
|
||||
if self.insertedFrames then
|
||||
for i = 1, #self.insertedFrames do
|
||||
self.insertedFrames[i]:Hide();
|
||||
end
|
||||
end
|
||||
self.insertedFrames = nil;
|
||||
end
|
||||
|
||||
function SharedTooltip_SetBackdropStyle(self, style, embedded)
|
||||
if embedded or self.IsEmbedded then
|
||||
self:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b, 0)
|
||||
self:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b, 0);
|
||||
else
|
||||
self:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b, 1);
|
||||
self:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b, 1);
|
||||
end
|
||||
|
||||
if self.TopOverlay then
|
||||
if style and style.overlayAtlasTop then
|
||||
self.TopOverlay:SetAtlas(style.overlayAtlasTop, true);
|
||||
self.TopOverlay:SetScale(style.overlayAtlasTopScale or 1.0);
|
||||
self.TopOverlay:SetPoint("CENTER", self, "TOP", style.overlayAtlasTopXOffset or 0, style.overlayAtlasTopYOffset or 0);
|
||||
self.TopOverlay:Show();
|
||||
else
|
||||
self.TopOverlay:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
if self.BottomOverlay then
|
||||
if style and style.overlayAtlasBottom then
|
||||
self.BottomOverlay:SetAtlas(style.overlayAtlasBottom, true);
|
||||
self.BottomOverlay:SetScale(style.overlayAtlasBottomScale or 1.0);
|
||||
self.BottomOverlay:SetPoint("CENTER", self, "BOTTOM", style.overlayAtlasBottomXOffset or 0, style.overlayAtlasBottomYOffset or 0);
|
||||
self.BottomOverlay:Show();
|
||||
else
|
||||
self.BottomOverlay:Hide();
|
||||
end
|
||||
end
|
||||
|
||||
if style and style.padding then
|
||||
self:SetPadding(style.padding.right, style.padding.bottom, style.padding.left, style.padding.top);
|
||||
end
|
||||
end
|
||||
|
||||
function GameTooltip_AddBlankLinesToTooltip(tooltip, numLines)
|
||||
if numLines ~= nil then
|
||||
for i = 1, numLines do
|
||||
tooltip:AddLine(" ");
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GameTooltip_AddBlankLineToTooltip(tooltip)
|
||||
GameTooltip_AddBlankLinesToTooltip(tooltip, 1);
|
||||
end
|
||||
|
||||
function GameTooltip_SetTitle(tooltip, text, overrideColor, wrap)
|
||||
local titleColor = overrideColor or HIGHLIGHT_FONT_COLOR;
|
||||
local r, g, b, a = titleColor:GetRGBA();
|
||||
if wrap == nil then
|
||||
wrap = 1;
|
||||
end
|
||||
tooltip:SetText(text, r, g, b, a, wrap);
|
||||
end
|
||||
|
||||
function GameTooltip_ShowDisabledTooltip(tooltip, owner, text, tooltipAnchor)
|
||||
tooltip:SetOwner(owner, tooltipAnchor);
|
||||
|
||||
local wrap = 1;
|
||||
GameTooltip_SetTitle(tooltip, text, RED_FONT_COLOR, wrap);
|
||||
|
||||
tooltip:Show();
|
||||
end
|
||||
|
||||
function GameTooltip_AddNormalLine(tooltip, text, wrap, leftOffset)
|
||||
GameTooltip_AddColoredLine(tooltip, text, NORMAL_FONT_COLOR, wrap, leftOffset);
|
||||
end
|
||||
|
||||
function GameTooltip_AddHighlightLine(tooltip, text, wrap, leftOffset)
|
||||
GameTooltip_AddColoredLine(tooltip, text, HIGHLIGHT_FONT_COLOR, wrap, leftOffset);
|
||||
end
|
||||
|
||||
function GameTooltip_AddInstructionLine(tooltip, text, wrap, leftOffset)
|
||||
GameTooltip_AddColoredLine(tooltip, text, GREEN_FONT_COLOR, wrap, leftOffset);
|
||||
end
|
||||
|
||||
function GameTooltip_AddErrorLine(tooltip, text, wrap, leftOffset)
|
||||
GameTooltip_AddColoredLine(tooltip, text, RED_FONT_COLOR, wrap, leftOffset);
|
||||
end
|
||||
|
||||
function GameTooltip_AddDisabledLine(tooltip, text, wrap, leftOffset)
|
||||
GameTooltip_AddColoredLine(tooltip, text, DISABLED_FONT_COLOR, wrap, leftOffset);
|
||||
end
|
||||
|
||||
function GameTooltip_AddColoredLine(tooltip, text, color, wrap, leftOffset)
|
||||
local r, g, b = color:GetRGB();
|
||||
if wrap == nil then
|
||||
wrap = 1;
|
||||
end
|
||||
tooltip:AddLine(text, r, g, b, wrap, leftOffset);
|
||||
end
|
||||
|
||||
function GameTooltip_AddColoredDoubleLine(tooltip, leftText, rightText, leftColor, rightColor, wrap)
|
||||
local leftR, leftG, leftB = leftColor:GetRGB();
|
||||
local rightR, rightG, rightB = rightColor:GetRGB();
|
||||
if wrap == nil then
|
||||
wrap = 1;
|
||||
end
|
||||
tooltip:AddDoubleLine(leftText, rightText, leftR, leftG, leftB, rightR, rightG, rightB, wrap);
|
||||
end
|
||||
|
||||
function GameTooltip_InsertFrame(tooltipFrame, frame, verticalPadding)
|
||||
verticalPadding = verticalPadding or 0;
|
||||
|
||||
local textSpacing = 2;
|
||||
local textHeight = Round(_G[tooltipFrame:GetName().."TextLeft2"]:GetLineHeight());
|
||||
local neededHeight = Round(frame:GetHeight() + verticalPadding);
|
||||
local numLinesNeeded = math.ceil(neededHeight / (textHeight + textSpacing));
|
||||
local currentLine = tooltipFrame:NumLines();
|
||||
GameTooltip_AddBlankLinesToTooltip(tooltipFrame, numLinesNeeded);
|
||||
frame:SetParent(tooltipFrame);
|
||||
frame:ClearAllPoints();
|
||||
frame:SetPoint("TOPLEFT", tooltipFrame:GetName().."TextLeft"..(currentLine + 1), "TOPLEFT", 0, -verticalPadding);
|
||||
if not tooltipFrame.insertedFrames then
|
||||
tooltipFrame.insertedFrames = { };
|
||||
end
|
||||
local frameWidth = frame:GetWidth();
|
||||
if tooltipFrame:GetMinimumWidth() < frameWidth then
|
||||
tooltipFrame:SetMinimumWidth(frameWidth);
|
||||
end
|
||||
frame:Show();
|
||||
tinsert(tooltipFrame.insertedFrames, frame);
|
||||
-- return space taken so inserted frame can resize if needed
|
||||
return (numLinesNeeded * textHeight) + (numLinesNeeded - 1) * textSpacing;
|
||||
end
|
||||
|
||||
DisabledTooltipButtonMixin = {};
|
||||
|
||||
function DisabledTooltipButtonMixin:OnEnter()
|
||||
if not self:IsEnabled() then
|
||||
local disabledTooltip, disabledTooltipAnchor = self:GetDisabledTooltip();
|
||||
if disabledTooltip ~= nil then
|
||||
GameTooltip_ShowDisabledTooltip(GetAppropriateTooltip(), self, disabledTooltip, disabledTooltipAnchor);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DisabledTooltipButtonMixin:OnLeave()
|
||||
local tooltip = GetAppropriateTooltip();
|
||||
tooltip:Hide();
|
||||
end
|
||||
|
||||
function DisabledTooltipButtonMixin:SetDisabledTooltip(disabledTooltip, disabledTooltipAnchor)
|
||||
self.disabledTooltip = disabledTooltip;
|
||||
self.disabledTooltipAnchor = disabledTooltipAnchor;
|
||||
end
|
||||
|
||||
function DisabledTooltipButtonMixin:GetDisabledTooltip()
|
||||
return self.disabledTooltip, self.disabledTooltipAnchor;
|
||||
end
|
||||
|
||||
function DisabledTooltipButtonMixin:SetDisabledState(disabled, disabledTooltip, disabledTooltipAnchor)
|
||||
self:SetEnabled(not disabled);
|
||||
self:SetDisabledTooltip(disabledTooltip, disabledTooltipAnchor);
|
||||
end
|
||||
@@ -0,0 +1,191 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="SharedTooltipTemplates.lua"/>
|
||||
|
||||
<FontString name="TooltipTextLeftTemplate" inherits="GameTooltipText" justifyH="LEFT" hidden="true" virtual="true"/>
|
||||
<FontString name="TooltipTextRightTemplate" inherits="GameTooltipText" justifyH="RIGHT" hidden="true" virtual="true"/>
|
||||
|
||||
<Texture name="TooltipTextureTemplate" hidden="true" virtual="true">
|
||||
<Size x="12" y="12"/>
|
||||
</Texture>
|
||||
|
||||
<GameTooltip name="SharedTooltipTemplate" clampedToScreen="true" frameStrata="TOOLTIP" hidden="true" virtual="true">
|
||||
<Attributes>
|
||||
<Attribute name="textLeft1Font" value="GameTooltipHeaderText" type="string"/>
|
||||
<Attribute name="textRight1Font" value="GameTooltipHeaderText" type="string"/>
|
||||
<Attribute name="textLeft2Font" value="GameTooltipText" type="string"/>
|
||||
<Attribute name="textRight2Font" value="GameTooltipText" type="string"/>
|
||||
<Attribute name="layoutType" value="TooltipDefaultLayout" type="string"/>
|
||||
</Attributes>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
|
||||
<EdgeSize>
|
||||
<AbsValue val="16"/>
|
||||
</EdgeSize>
|
||||
<TileSize>
|
||||
<AbsValue val="16"/>
|
||||
</TileSize>
|
||||
<BackgroundInsets>
|
||||
<AbsInset left="5" right="5" top="5" bottom="5"/>
|
||||
</BackgroundInsets>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<Texture parentKey="TopOverlay" hidden="true">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="TOP" />
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture parentKey="BottomOverlay" hidden="true">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativePoint="BOTTOM" />
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="ARTWORK">
|
||||
<FontString name="$parentTextLeft1" parentKey="TextLeft1" inherits="TooltipTextLeftTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" x="10" y="-10"/>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parentTextRight1" parentKey="TextRight1" inherits="TooltipTextRightTemplate" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="$parentTextLeft1" relativePoint="LEFT" x="40" y="0"/>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parentTextLeft2" parentKey="TextLeft2" inherits="TooltipTextLeftTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentTextLeft1" relativePoint="BOTTOMLEFT" x="0" y="-2"/>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString name="$parentTextRight2" parentKey="TextRight2" inherits="TooltipTextRightTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="RIGHT" relativeTo="$parentTextLeft2" relativePoint="LEFT" x="40" y="0"/>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<Texture name="$parentTexture1" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture2" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture3" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture4" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture5" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture6" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture7" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture8" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture9" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture10" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture11" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture12" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture13" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture14" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture15" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture16" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture17" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture18" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture19" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture20" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture21" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture22" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture23" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture24" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture25" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture26" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture27" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture28" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture29" inherits="TooltipTextureTemplate"/>
|
||||
<Texture name="$parentTexture30" inherits="TooltipTextureTemplate"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Scripts>
|
||||
<OnLoad function="SharedTooltip_OnLoad"/>
|
||||
<OnHide function="SharedTooltip_OnHide"/>
|
||||
<OnTooltipSetDefaultAnchor function="SharedTooltip_SetDefaultAnchor"/>
|
||||
<OnTooltipCleared function="SharedTooltip_ClearInsertedFrames"/>
|
||||
</Scripts>
|
||||
</GameTooltip>
|
||||
|
||||
<GameTooltip name="SharedNoHeaderTooltipTemplate" inherits="SharedTooltipTemplate" virtual="true">
|
||||
<Attributes>
|
||||
<Attribute name="textLeft1Font" value="GameTooltipText" type="string"/>
|
||||
<Attribute name="textRight1Font" value="GameTooltipText" type="string"/>
|
||||
</Attributes>
|
||||
</GameTooltip>
|
||||
|
||||
<Frame name="TooltipBorderedFrameTemplate" virtual="true">
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture parentKey="BorderTopLeft" name="$parentBorderTopLeft" file="Interface\Tooltips\UI-Tooltip-TL">
|
||||
<Size x="8" y="8"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture parentKey="BorderTopRight" name="$parentBorderTopRight" file="Interface\Tooltips\UI-Tooltip-TR">
|
||||
<Size x="8" y="8"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture parentKey="BorderBottomRight" name="$parentBorderBottomRight" file="Interface\Tooltips\UI-Tooltip-BR">
|
||||
<Size x="8" y="8"/>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMRIGHT"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture parentKey="BorderBottomLeft" name="$parentBorderBottomLeft" file="Interface\Tooltips\UI-Tooltip-BL">
|
||||
<Size x="8" y="8"/>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture parentKey="BorderTop" file="Interface\Tooltips\UI-Tooltip-T">
|
||||
<Size x="8" y="8"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentBorderTopLeft" relativePoint="TOPRIGHT"/>
|
||||
<Anchor point="TOPRIGHT" relativeTo="$parentBorderTopRight" relativePoint="TOPLEFT"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture parentKey="BorderRight" file="Interface\Tooltips\UI-Tooltip-R">
|
||||
<Size x="8" y="8"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeTo="$parentBorderTopRight" relativePoint="BOTTOMRIGHT"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="$parentBorderBottomRight" relativePoint="TOPRIGHT"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture parentKey="BorderBottom" file="Interface\Tooltips\UI-Tooltip-B">
|
||||
<Size x="8" y="8"/>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="$parentBorderBottomLeft" relativePoint="BOTTOMRIGHT"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="$parentBorderBottomRight" relativePoint="BOTTOMLEFT"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture parentKey="BorderLeft" file="Interface\Tooltips\UI-Tooltip-L">
|
||||
<Size x="8" y="8"/>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentBorderTopLeft" relativePoint="BOTTOMLEFT"/>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="$parentBorderBottomLeft" relativePoint="TOPLEFT"/>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture parentKey="Background">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentBorderTopLeft" relativePoint="BOTTOMRIGHT"/>
|
||||
<Anchor point="BOTTOMRIGHT" relativeTo="$parentBorderBottomRight" relativePoint="TOPLEFT"/>
|
||||
</Anchors>
|
||||
<Color r="0" g="0" b="0" a="0.8"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</Frame>
|
||||
|
||||
<Button name="DisabledTooltipButtonTemplate" motionScriptsWhileDisabled="true" virtual="true">
|
||||
<Scripts>
|
||||
<OnLoad>
|
||||
Mixin(self, DisabledTooltipButtonMixin)
|
||||
</OnLoad>
|
||||
<OnEnter>
|
||||
self:OnEnter()
|
||||
</OnEnter>
|
||||
<OnLeave>
|
||||
self:OnLeave()
|
||||
</OnLeave>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Ui>
|
||||
@@ -0,0 +1,365 @@
|
||||
-- SOUND KITS
|
||||
SOUNDKIT = {
|
||||
LOOT_WINDOW_COIN_SOUND = 120,
|
||||
INTERFACE_SOUND_LOST_TARGET_UNIT = 684,
|
||||
GS_TITLE_OPTIONS = 778,
|
||||
GS_TITLE_CREDITS = 790,
|
||||
GS_TITLE_OPTION_OK = 798,
|
||||
GS_TITLE_OPTION_EXIT = 799,
|
||||
GS_LOGIN = 800,
|
||||
GS_LOGIN_NEW_ACCOUNT = 801,
|
||||
GS_LOGIN_CHANGE_REALM_OK = 805,
|
||||
GS_LOGIN_CHANGE_REALM_CANCEL = 807,
|
||||
GS_CHARACTER_SELECTION_ENTER_WORLD = 809,
|
||||
GS_CHARACTER_SELECTION_DEL_CHARACTER = 810,
|
||||
GS_CHARACTER_SELECTION_ACCT_OPTIONS = 811,
|
||||
GS_CHARACTER_SELECTION_EXIT = 812,
|
||||
GS_CHARACTER_SELECTION_CREATE_NEW = 813,
|
||||
GS_CHARACTER_CREATION_CLASS = 814,
|
||||
GS_CHARACTER_CREATION_LOOK = 817,
|
||||
GS_CHARACTER_CREATION_CREATE_CHAR = 818,
|
||||
GS_CHARACTER_CREATION_CANCEL = 819,
|
||||
IG_MINIMAP_OPEN = 821,
|
||||
IG_MINIMAP_CLOSE = 822,
|
||||
IG_MINIMAP_ZOOM_IN = 823,
|
||||
IG_MINIMAP_ZOOM_OUT = 824,
|
||||
IG_CHAT_EMOTE_BUTTON = 825,
|
||||
IG_CHAT_SCROLL_UP = 826,
|
||||
IG_CHAT_SCROLL_DOWN = 827,
|
||||
IG_CHAT_BOTTOM = 828,
|
||||
IG_SPELLBOOK_OPEN = 829,
|
||||
IG_SPELLBOOK_CLOSE = 830,
|
||||
IG_ABILITY_OPEN = 834,
|
||||
IG_ABILITY_CLOSE = 835,
|
||||
IG_ABILITY_PAGE_TURN = 836,
|
||||
IG_ABILITY_ICON_DROP = 838,
|
||||
IG_CHARACTER_INFO_OPEN = 839,
|
||||
IG_CHARACTER_INFO_CLOSE = 840,
|
||||
IG_CHARACTER_INFO_TAB = 841,
|
||||
IG_QUEST_LOG_OPEN = 844,
|
||||
IG_QUEST_LOG_CLOSE = 845,
|
||||
IG_QUEST_LOG_ABANDON_QUEST = 846,
|
||||
IG_MAINMENU_OPEN = 850,
|
||||
IG_MAINMENU_CLOSE = 851,
|
||||
IG_MAINMENU_OPTION = 852,
|
||||
IG_MAINMENU_LOGOUT = 853,
|
||||
IG_MAINMENU_QUIT = 854,
|
||||
IG_MAINMENU_CONTINUE = 855,
|
||||
IG_MAINMENU_OPTION_CHECKBOX_ON = 856,
|
||||
IG_MAINMENU_OPTION_CHECKBOX_OFF = 857,
|
||||
IG_MAINMENU_OPTION_FAER_TAB = 858,
|
||||
IG_INVENTORY_ROTATE_CHARACTER = 861,
|
||||
IG_BACKPACK_OPEN = 862,
|
||||
IG_BACKPACK_CLOSE = 863,
|
||||
IG_BACKPACK_COIN_SELECT = 864,
|
||||
IG_BACKPACK_COIN_OK = 865,
|
||||
IG_BACKPACK_COIN_CANCEL = 866,
|
||||
IG_CHARACTER_NPC_SELECT = 867,
|
||||
IG_CREATURE_NEUTRAL_SELECT = 871,
|
||||
IG_CREATURE_AGGRO_SELECT = 873,
|
||||
IG_QUEST_LIST_OPEN = 875,
|
||||
IG_QUEST_LIST_CLOSE = 876,
|
||||
IG_QUEST_LIST_SELECT = 877,
|
||||
IG_QUEST_LIST_COMPLETE = 878,
|
||||
IG_QUEST_CANCEL = 879,
|
||||
IG_PLAYER_INVITE = 880,
|
||||
MONEY_FRAME_OPEN = 891,
|
||||
MONEY_FRAME_CLOSE = 892,
|
||||
U_CHAT_SCROLL_BUTTON = 1115,
|
||||
PUT_DOWN_SMALL_CHAIN = 1212,
|
||||
LOOT_WINDOW_OPEN_EMPTY = 1264,
|
||||
TELL_MESSAGE = 3081,
|
||||
MAP_PING = 3175,
|
||||
FISHING_REEL_IN = 3407,
|
||||
IG_PVP_UPDATE = 4574,
|
||||
AUCTION_WINDOW_OPEN = 5274,
|
||||
AUCTION_WINDOW_CLOSE = 5275,
|
||||
TUTORIAL_POPUP = 7355,
|
||||
ITEM_REPAIR = 7994,
|
||||
PVP_ENTER_QUEUE = 8458,
|
||||
PVP_THROUGH_QUEUE = 8459,
|
||||
KEY_RING_OPEN = 8938,
|
||||
KEY_RING_CLOSE = 8939,
|
||||
RAID_WARNING = 8959,
|
||||
READY_CHECK = 8960,
|
||||
GLUESCREEN_INTRO = 9902,
|
||||
AMB_GLUESCREEN_HUMAN = 9903,
|
||||
AMB_GLUESCREEN_ORC = 9905,
|
||||
AMB_GLUESCREEN_TAUREN = 9906,
|
||||
AMB_GLUESCREEN_DWARF = 9907,
|
||||
AMB_GLUESCREEN_NIGHTELF = 9908,
|
||||
AMB_GLUESCREEN_UNDEAD = 9909,
|
||||
AMB_GLUESCREEN_BLOODELF = 9910,
|
||||
AMB_GLUESCREEN_DRAENEI = 9911,
|
||||
JEWEL_CRAFTING_FINALIZE = 10590,
|
||||
MENU_CREDITS01 = 10763,
|
||||
MENU_CREDITS02 = 10804,
|
||||
GUILD_VAULT_OPEN = 12188,
|
||||
GUILD_VAULT_CLOSE = 12189,
|
||||
RAID_BOSS_EMOTE_WARNING = 12197,
|
||||
GUILD_BANK_OPEN_BAG = 12206,
|
||||
GS_LICH_KING = 12765,
|
||||
ALARM_CLOCK_WARNING_2 = 12867,
|
||||
ALARM_CLOCK_WARNING_3 = 12889,
|
||||
MENU_CREDITS03 = 13822,
|
||||
ACHIEVEMENT_MENU_OPEN = 13832,
|
||||
ACHIEVEMENT_MENU_CLOSE = 13833,
|
||||
BARBERSHOP_HAIRCUT = 13873,
|
||||
BARBERSHOP_SIT = 14148,
|
||||
GM_CHAT_WARNING = 15273,
|
||||
LFG_REWARDS = 17316,
|
||||
LFG_ROLE_CHECK = 17317,
|
||||
LFG_DENIED = 17341,
|
||||
UI_BNET_TOAST = 18019,
|
||||
ALARM_CLOCK_WARNING_1 = 18871,
|
||||
AMB_GLUESCREEN_WORGEN = 20169,
|
||||
AMB_GLUESCREEN_GOBLIN = 20170,
|
||||
AMB_GLUESCREEN_TROLL = 21136,
|
||||
AMB_GLUESCREEN_GNOME = 21137,
|
||||
UI_POWER_AURA_GENERIC = 23287,
|
||||
UI_REFORGING_REFORGE = 23291,
|
||||
UI_AUTO_QUEST_COMPLETE = 23404,
|
||||
GS_CATACLYSM = 23640,
|
||||
MENU_CREDITS04 = 23812,
|
||||
UI_BATTLEGROUND_COUNTDOWN_TIMER = 25477,
|
||||
UI_BATTLEGROUND_COUNTDOWN_FINISHED = 25478,
|
||||
UI_VOID_STORAGE_UNLOCK = 25711,
|
||||
UI_VOID_STORAGE_DEPOSIT = 25712,
|
||||
UI_VOID_STORAGE_WITHDRAW = 25713,
|
||||
UI_TRANSMOGRIFY_UNDO = 25715,
|
||||
UI_ETHEREAL_WINDOW_OPEN = 25716,
|
||||
UI_ETHEREAL_WINDOW_CLOSE = 25717,
|
||||
UI_TRANSMOGRIFY_REDO = 25738,
|
||||
UI_VOID_STORAGE_BOTH = 25744,
|
||||
AMB_GLUESCREEN_PANDAREN = 25848,
|
||||
MUS_50_HEART_OF_PANDARIA_MAINTITLE = 28509,
|
||||
UI_PET_BATTLES_TRAP_READY = 28814,
|
||||
UI_EPICLOOT_TOAST = 31578,
|
||||
UI_BONUS_LOOT_ROLL_START = 31579,
|
||||
UI_BONUS_LOOT_ROLL_LOOP = 31580,
|
||||
UI_BONUS_LOOT_ROLL_END = 31581,
|
||||
UI_PET_BATTLE_START = 31584,
|
||||
UI_SCENARIO_ENDING = 31754,
|
||||
UI_SCENARIO_STAGE_END = 31757,
|
||||
MENU_CREDITS05 = 32015,
|
||||
UI_PET_BATTLE_CAMERA_MOVE_IN = 32047,
|
||||
UI_PET_BATTLE_CAMERA_MOVE_OUT = 32052,
|
||||
AMB_50_GLUESCREEN_ALLIANCE = 32412,
|
||||
AMB_50_GLUESCREEN_HORDE = 32413,
|
||||
AMB_50_GLUESCREEN_PANDAREN_NEUTRAL = 32414,
|
||||
UI_CHALLENGES_NEW_RECORD = 33338,
|
||||
MENU_CREDITS06 = 34020,
|
||||
UI_LOSS_OF_CONTROL_START = 34468,
|
||||
UI_PET_BATTLES_PVP_THROUGH_QUEUE = 36609,
|
||||
AMB_GLUESCREEN_DEATHKNIGHT = 37056,
|
||||
UI_RAID_BOSS_WHISPER_WARNING = 37666,
|
||||
UI_DIG_SITE_COMPLETION_TOAST = 38326,
|
||||
UI_IG_STORE_PAGE_NAV_BUTTON = 39511,
|
||||
UI_IG_STORE_WINDOW_OPEN_BUTTON = 39512,
|
||||
UI_IG_STORE_WINDOW_CLOSE_BUTTON = 39513,
|
||||
UI_IG_STORE_CANCEL_BUTTON = 39514,
|
||||
UI_IG_STORE_BUY_BUTTON = 39515,
|
||||
UI_IG_STORE_CONFIRM_PURCHASE_BUTTON = 39516,
|
||||
UI_IG_STORE_PURCHASE_DELIVERED_TOAST_01 = 39517,
|
||||
MUS_60_MAIN_TITLE = 40169,
|
||||
UI_GARRISON_MISSION_COMPLETE_ENCOUNTER_FAIL = 43501,
|
||||
UI_GARRISON_MISSION_COMPLETE_MISSION_SUCCESS = 43502,
|
||||
UI_GARRISON_MISSION_COMPLETE_MISSION_FAIL_STINGER = 43503,
|
||||
UI_GARRISON_MISSION_THREAT_COUNTERED = 43505,
|
||||
UI_GARRISON_MISSION_100_PERCENT_CHANCE_REACHED_NOT_USED = 43507,
|
||||
UI_QUEST_ROLLING_FORWARD_01 = 43936,
|
||||
UI_BAG_SORTING_01 = 43937,
|
||||
UI_TOYBOX_TABS = 43938,
|
||||
UI_GARRISON_TOAST_INVASION_ALERT = 44292,
|
||||
UI_GARRISON_TOAST_MISSION_COMPLETE = 44294,
|
||||
UI_GARRISON_TOAST_BUILDING_COMPLETE = 44295,
|
||||
UI_GARRISON_TOAST_FOLLOWER_GAINED = 44296,
|
||||
UI_GARRISON_NAV_TABS = 44297,
|
||||
UI_GARRISON_GARRISON_REPORT_OPEN = 44298,
|
||||
UI_GARRISON_GARRISON_REPORT_CLOSE = 44299,
|
||||
UI_GARRISON_ARCHITECT_TABLE_OPEN = 44300,
|
||||
UI_GARRISON_ARCHITECT_TABLE_CLOSE = 44301,
|
||||
UI_GARRISON_ARCHITECT_TABLE_UPGRADE = 44302,
|
||||
UI_GARRISON_ARCHITECT_TABLE_UPGRADE_CANCEL = 44304,
|
||||
UI_GARRISON_ARCHITECT_TABLE_UPGRADE_START = 44305,
|
||||
UI_GARRISON_ARCHITECT_TABLE_PLOT_SELECT = 44306,
|
||||
UI_GARRISON_ARCHITECT_TABLE_BUILDING_SELECT = 44307,
|
||||
UI_GARRISON_ARCHITECT_TABLE_BUILDING_PLACEMENT = 44308,
|
||||
UI_GARRISON_COMMAND_TABLE_OPEN = 44311,
|
||||
UI_GARRISON_COMMAND_TABLE_CLOSE = 44312,
|
||||
UI_GARRISON_COMMAND_TABLE_MISSION_CLOSE = 44313,
|
||||
UI_GARRISON_COMMAND_TABLE_NAV_NEXT = 44314,
|
||||
UI_GARRISON_COMMAND_TABLE_SELECT_MISSION = 44315,
|
||||
UI_GARRISON_COMMAND_TABLE_SELECT_FOLLOWER = 44316,
|
||||
UI_GARRISON_COMMAND_TABLE_FOLLOWER_ABILITY_OPEN = 44317,
|
||||
UI_GARRISON_COMMAND_TABLE_FOLLOWER_ABILITY_CLOSE = 44318,
|
||||
UI_GARRISON_COMMAND_TABLE_ASSIGN_FOLLOWER = 44319,
|
||||
UI_GARRISON_COMMAND_TABLE_UNASSIGN_FOLLOWER = 44320,
|
||||
UI_GARRISON_COMMAND_TABLE_SLOT_CHAMPION = 72546,
|
||||
UI_GARRISON_COMMAND_TABLE_SLOT_TROOP = 72547,
|
||||
UI_GARRISON_COMMAND_TABLE_REDUCED_SUCCESS_CHANCE = 44321,
|
||||
UI_GARRISON_COMMAND_TABLE_100_SUCCESS = 44322,
|
||||
UI_GARRISON_COMMAND_TABLE_MISSION_START = 44323,
|
||||
UI_GARRISON_COMMAND_TABLE_VIEW_MISSION_REPORT = 44324,
|
||||
UI_GARRISON_COMMAND_TABLE_MISSION_SUCCESS_STINGER = 44330,
|
||||
UI_GARRISON_COMMAND_TABLE_CHEST_UNLOCK = 44331,
|
||||
UI_GARRISON_COMMAND_TABLE_CHEST_UNLOCK_GOLD_SUCCESS = 44332,
|
||||
UI_GARRISON_MONUMENTS_OPEN = 44344,
|
||||
UI_BONUS_EVENT_SYSTEM_VIGNETTES = 45142,
|
||||
UI_GARRISON_COMMAND_TABLE_FOLLOWER_LEVEL_UP = 46893,
|
||||
UI_GARRISON_ARCHITECT_TABLE_BUILDING_PLACEMENT_ERROR = 47355,
|
||||
UI_GARRISON_MONUMENTS_CLOSE = 47373,
|
||||
AMB_GLUESCREEN_WARLORDS_OF_DRAENOR = 47544,
|
||||
MUS_1_0_MAINTITLE_ORIGINAL = 47598,
|
||||
UI_GROUP_FINDER_RECEIVE_APPLICATION = 47615,
|
||||
UI_GARRISON_MISSION_ENCOUNTER_ANIMATION_GENERIC = 47704,
|
||||
UI_GARRISON_START_WORK_ORDER = 47972,
|
||||
UI_GARRISON_SHIPMENTS_WINDOW_OPEN = 48191,
|
||||
UI_GARRISON_SHIPMENTS_WINDOW_CLOSE = 48192,
|
||||
UI_GARRISON_MONUMENTS_NAV = 48942,
|
||||
UI_RAID_BOSS_DEFEATED = 50111,
|
||||
UI_PERSONAL_LOOT_BANNER = 50893,
|
||||
UI_GARRISON_FOLLOWER_LEARN_TRAIT = 51324,
|
||||
UI_GARRISON_SHIPYARD_PLACE_CARRIER = 51385,
|
||||
UI_GARRISON_SHIPYARD_PLACE_GALLEON = 51387,
|
||||
UI_GARRISON_SHIPYARD_PLACE_DREADNOUGHT = 51388,
|
||||
UI_GARRISON_SHIPYARD_PLACE_SUBMARINE = 51389,
|
||||
UI_GARRISON_SHIPYARD_PLACE_LANDING_CRAFT = 51390,
|
||||
UI_GARRISON_SHIPYARD_START_MISSION = 51401,
|
||||
UI_RAID_LOOT_TOAST_LESSER_ITEM_WON = 51402,
|
||||
UI_WARFORGED_ITEM_LOOT_TOAST = 51561,
|
||||
UI_GARRISON_COMMAND_TABLE_INCREASED_SUCCESS_CHANCE = 51570,
|
||||
UI_GARRISON_SHIPYARD_DECOMISSION_SHIP = 51871,
|
||||
UI_70_ARTIFACT_FORGE_TRAIT_FIRST_TRAIT = 54126,
|
||||
UI_70_ARTIFACT_FORGE_RELIC_PLACE = 54128,
|
||||
UI_70_ARTIFACT_FORGE_APPEARANCE_COLOR_SELECT = 54130,
|
||||
UI_70_ARTIFACT_FORGE_APPEARANCE_LOCKED = 54131,
|
||||
UI_70_ARTIFACT_FORGE_APPEARANCE_APPEARANCE_CHANGE = 54132,
|
||||
UI_70_ARTIFACT_FORGE_TOAST_TRAIT_AVAILABLE = 54133,
|
||||
UI_70_ARTIFACT_FORGE_APPEARANCE_APPEARANCE_UNLOCK = 54139,
|
||||
UI_70_ARTIFACT_FORGE_TRAIT_GOLD_TRAIT = 54125,
|
||||
UI_72_ARTIFACT_FORGE_FINAL_TRAIT_UNLOCKED = 83682,
|
||||
UI_70_ARTIFACT_FORGE_TRAIT_FINALRANK = 54127,
|
||||
UI_70_ARTIFACT_FORGE_TRAIT_RANKUP = 54129,
|
||||
AMB_GLUESCREEN_DEMONHUNTER = 56352,
|
||||
MUS_70_MAIN_TITLE = 56353,
|
||||
MENU_CREDITS07 = 56354,
|
||||
UI_TRANSMOG_ITEM_CLICK = 62538,
|
||||
UI_TRANSMOG_PAGE_TURN = 62539,
|
||||
UI_TRANSMOG_GEAR_SLOT_CLICK = 62540,
|
||||
UI_TRANSMOG_REVERTING_GEAR_SLOT = 62541,
|
||||
UI_TRANSMOG_APPLY = 62542,
|
||||
UI_TRANSMOG_CLOSE_WINDOW = 62543,
|
||||
UI_TRANSMOG_OPEN_WINDOW = 62544,
|
||||
UI_LEGENDARY_LOOT_TOAST = 63971,
|
||||
UI_STORE_UNWRAP = 64329,
|
||||
AMB_GLUESCREEN_LEGION = 71535,
|
||||
UI_MISSION_200_PERCENT = 72548,
|
||||
UI_MISSION_MAP_ZOOM = 72549,
|
||||
UI_70_BOOST_THANKSFORPLAYING_SMALLER = 72978,
|
||||
UI_70_BOOST_THANKSFORPLAYING = 72977,
|
||||
UI_WORLDQUEST_START = 73275,
|
||||
UI_WORLDQUEST_MAP_SELECT = 73276,
|
||||
UI_WORLDQUEST_COMPLETE = 73277,
|
||||
UI_ORDERHALL_TALENT_SELECT = 73279,
|
||||
UI_ORDERHALL_TALENT_READY_TOAST = 73280,
|
||||
UI_ORDERHALL_TALENT_READY_CHECK = 73281,
|
||||
UI_ORDERHALL_TALENT_NUKE_FROM_ORBIT = 73282,
|
||||
UI_ORDERHALL_TALENT_WINDOW_OPEN = 73914,
|
||||
UI_ORDERHALL_TALENT_WINDOW_CLOSE = 73915,
|
||||
UI_PROFESSIONS_WINDOW_OPEN = 73917,
|
||||
UI_PROFESSIONS_WINDOW_CLOSE = 73918,
|
||||
UI_PROFESSIONS_NEW_RECIPE_LEARNED_TOAST = 73919,
|
||||
UI_70_CHALLENGE_MODE_SOCKET_PAGE_OPEN = 74421,
|
||||
UI_70_CHALLENGE_MODE_SOCKET_PAGE_CLOSE = 74423,
|
||||
UI_70_CHALLENGE_MODE_SOCKET_PAGE_SOCKET = 74431,
|
||||
UI_70_CHALLENGE_MODE_SOCKET_PAGE_ACTIVATE_BUTTON = 74432,
|
||||
UI_70_CHALLENGE_MODE_KEYSTONE_UPGRADE = 74437,
|
||||
UI_70_CHALLENGE_MODE_NEW_RECORD = 74438,
|
||||
UI_70_CHALLENGE_MODE_SOCKET_PAGE_REMOVE_KEYSTONE = 74525,
|
||||
UI_70_CHALLENGE_MODE_COMPLETE_NO_UPGRADE = 74526,
|
||||
UI_MISSION_SUCCESS_CHEERS = 74702,
|
||||
UI_PVP_HONOR_PRESTIGE_OPEN_WINDOW = 76995,
|
||||
UI_PVP_HONOR_PRESTIGE_WINDOW_CLOSE = 77002,
|
||||
UI_PVP_HONOR_PRESTIGE_RANK_UP = 77003,
|
||||
UI_71_SOCIAL_QUEUEING_TOAST = 79739,
|
||||
UI_72_ARTIFACT_FORGE_ACTIVATE_FINAL_TIER = 83681,
|
||||
UI_72_BUILDINGS_CONTRIBUTE_POWER_MENU_CLICK = 84240,
|
||||
UI_72_BUILDING_CONTRIBUTION_TABLE_OPEN = 84368,
|
||||
UI_72_BUILDINGS_CONTRIBUTION_TABLE_CLOSE = 84369,
|
||||
UI_72_BUILDINGS_CONTRIBUTE_RESOURCES = 84378,
|
||||
UI_72_ARTIFACT_FORGE_FINAL_TRAIT_REFUND_START = 83684,
|
||||
UI_72_ARTIFACT_FORGE_FINAL_TRAIT_REFUND_LOOP = 83685,
|
||||
UI_73_ARTIFACT_RELICS_TRAIT_SELECT_AND_REVEAL = 89685,
|
||||
UI_73_ARTIFACT_RELICS_TRAIT_SELECT_ONLY = 89686,
|
||||
UI_73_ARTIFACT_RELICS_TRAIT_REVEAL_ONLY = 90080,
|
||||
PUT_DOWN_GEMS = 1204,
|
||||
PICK_UP_GEMS = 1221,
|
||||
UI_73_ARTIFACT_OVERLOADED_HIGHEST = 97301,
|
||||
UI_73_ARTIFACT_OVERLOADED_HIGH = 97471,
|
||||
UI_73_ARTIFACT_OVERLOADED_MEDIUM = 97470,
|
||||
UI_73_ARTIFACT_OVERLOADED_LOW = 97469,
|
||||
UI_73_ARTIFACT_OVERLOADED_ORB_HIGHEST = 97559,
|
||||
UI_73_ARTIFACT_OVERLOADED_ORB_HIGH = 97558,
|
||||
UI_73_ARTIFACT_OVERLOADED_ORB_MEDIUM = 97557,
|
||||
UI_73_ARTIFACT_OVERLOADED_ORB_LOW = 97556,
|
||||
UI_73_ARTIFACT_OVERLOADED_ORB_IMPACT_MEDIUM = 97597,
|
||||
UI_73_ARTIFACT_OVERLOADED_ORB_IMPACT_LOW = 97598,
|
||||
AMB_GLUESCREEN_VOIDELF = 97324,
|
||||
AMB_GLUESCREEN_LIGHTFORGEDDRAENEI = 97325,
|
||||
AMB_GLUESCREEN_NIGHTBORNE = 97326,
|
||||
AMB_GLUESCREEN_HIGHMOUNTAINTAUREN = 97327,
|
||||
UI_VOICECHAT_DEAFENOFF = 110990,
|
||||
UI_VOICECHAT_DEAFENON = 110989,
|
||||
UI_VOICECHAT_MUTEOFF = 110988,
|
||||
UI_VOICECHAT_MUTEON = 110987,
|
||||
UI_VOICECHAT_MUTEOTHEROFF = 111080,
|
||||
UI_VOICECHAT_MUTEOTHERON = 111081,
|
||||
UI_VOICECHAT_MEMBERLEAVECHANNEL = 110986,
|
||||
UI_VOICECHAT_MEMBERJOINCHANNEL = 110985,
|
||||
UI_VOICECHAT_STOPTALK = 110984,
|
||||
UI_VOICECHAT_TALKSTART = 110983,
|
||||
UI_VOICECHAT_LEAVECHANNEL = 110982,
|
||||
UI_VOICECHAT_JOINCHANNEL = 110981,
|
||||
AMB_GLUESCREEN_BATTLE_FOR_AZEROTH = 113894,
|
||||
AMB_GLUESCREEN_MAGHARORC = 113556,
|
||||
AMB_GLUESCREEN_DARKIRONDWARF = 113557,
|
||||
MUS_80_MAIN_TITLE = 113559,
|
||||
MENU_CREDITS08 = 113560,
|
||||
UI_AZERITE_EMPOWERED_ITEM_LOOT_TOAST = 118238,
|
||||
|
||||
UI_80_AZERITEARMOR_SELECTBUFF = 114993,
|
||||
UI_80_AZERITEARMOR_BUFFAVAILABLE = 116657,
|
||||
UI_80_AZERITEARMOR_ROTATION_LOOP = 114994,
|
||||
UI_80_AZERITEARMOR_ROTATIONENDS = 114995,
|
||||
UI_80_AZERITEARMOR_ROTATIONENDS_FINALTRAIT = 116233,
|
||||
UI_80_AZERITEARMOR_ROTATIONENDCLICKS = 115314,
|
||||
UI_80_AZERITEARMOR_ROTATIONSTARTCLICKS = 115358,
|
||||
UI_80_AZERITEARMOR_FIRSTTIMEFLOURISH = 118264,
|
||||
|
||||
UI_80_AZERITEARMOR_REFORGE_ETHEREALWINDOW_OPEN = 114990,
|
||||
UI_80_AZERITEARMOR_REFORGE_ETHEREALWINDOW_CLOSE = 114991,
|
||||
UI_80_AZERITEARMOR_REFORGE = 114992,
|
||||
UI_80_ISLANDS_AZERITECOLLECTION_START = 115003,
|
||||
UI_80_ISLANDS_AZERITECOLLECTION_LOOP = 115004,
|
||||
UI_80_ISLANDS_AZERITECOLLECTION_STOP = 115005,
|
||||
UI_80_SCRAPPING_WINDOW_CLOSE = 115487,
|
||||
UI_80_SCRAPPING_WINDOW_OPEN = 115488,
|
||||
UI_WARMODE_ACTIVATE = 118563,
|
||||
UI_WARMODE_DECTIVATE = 118564,
|
||||
|
||||
UI_80_ISLANDS_TABLE_OPEN = 118610,
|
||||
UI_80_ISLANDS_TABLE_CLOSE = 118611,
|
||||
UI_80_ISLANDS_TABLE_SELECT_DIFFICULTY = 118612,
|
||||
UI_80_ISLANDS_TABLE_FIND_GROUP = 118613,
|
||||
UI_80_ISLANDS_TABLE_FIND_GROUP_PVP = 118668,
|
||||
UI_80_ISLANDS_TUTORIAL_CLOSE = 119726,
|
||||
|
||||
UI_GARRISON_MISSION_COMPLETE_ENCOUNTER_CHANCE = 0, -- missing SoundKit entry!
|
||||
|
||||
UI_COUNTDOWN_BAR_STATE_STARTS = 158958,
|
||||
UI_COUNTDOWN_BAR_STATE_FINISHED = 158959,
|
||||
UI_COUNTDOWN_MEDIUM_NUMBER_FINISHED = 158960,
|
||||
UI_COUNTDOWN_TIMER = 191352,
|
||||
UI_COUNTDOWN_FINISHED = 158566,
|
||||
};
|
||||
@@ -0,0 +1,499 @@
|
||||
local ColumnWidthConstraints = {
|
||||
Fill = 1, -- Width will be distributed by available space.
|
||||
Fixed = 2, -- Width is specified when initializing the column.
|
||||
};
|
||||
|
||||
-- Any row or cell is expected to initialize itself in terms of the row data. The dataIndex is provided
|
||||
-- in case the derived mixin needs to make additional CAPI calls involving it's relative index. The row
|
||||
-- data may also be needed for a tooltip, so it will be assigned to the row and cells on update.
|
||||
TableBuilderElementMixin = {};
|
||||
|
||||
--Derive
|
||||
function TableBuilderElementMixin:Init(...)
|
||||
end
|
||||
|
||||
--Derive
|
||||
function TableBuilderElementMixin:Populate(rowData, dataIndex)
|
||||
end
|
||||
|
||||
TableBuilderCellMixin = CreateFromMixins(TableBuilderElementMixin);
|
||||
|
||||
--Derive
|
||||
function TableBuilderCellMixin:OnLineEnter()
|
||||
end
|
||||
|
||||
--Derive
|
||||
function TableBuilderCellMixin:OnLineLeave()
|
||||
end
|
||||
|
||||
|
||||
TableBuilderRowMixin = CreateFromMixins(TableBuilderElementMixin);
|
||||
|
||||
--Derive
|
||||
function TableBuilderRowMixin:OnLineEnter()
|
||||
end
|
||||
|
||||
--Derive
|
||||
function TableBuilderRowMixin:OnLineLeave()
|
||||
end
|
||||
|
||||
|
||||
function TableBuilderRowMixin:OnEnter()
|
||||
self:OnLineEnter();
|
||||
for i, cell in ipairs(self.cells) do
|
||||
cell:OnLineEnter();
|
||||
end
|
||||
end
|
||||
|
||||
function TableBuilderRowMixin:OnLeave()
|
||||
self:OnLineLeave();
|
||||
for i, cell in ipairs(self.cells) do
|
||||
cell:OnLineLeave();
|
||||
end
|
||||
end
|
||||
|
||||
-- Defines an entire column within the table builder, by default a column's sizing constraints are set to fill.
|
||||
TableBuilderColumnMixin = {};
|
||||
function TableBuilderColumnMixin:Init(table)
|
||||
self.cells = {};
|
||||
self.table = table;
|
||||
|
||||
local fillCoefficient = 1.0;
|
||||
local padding = 0;
|
||||
self:SetFillConstraints(fillCoefficient, padding);
|
||||
end
|
||||
|
||||
-- Constructs the header frame with an optional initializer.
|
||||
function TableBuilderColumnMixin:ConstructHeader(templateType, template, ...)
|
||||
local frame = self.table:ConstructHeader(templateType, template);
|
||||
self.headerFrame = frame;
|
||||
if frame.Init then
|
||||
frame:Init(...);
|
||||
end
|
||||
frame:Show();
|
||||
end
|
||||
|
||||
-- Constructs cells corresponding to each row with an optional initializer.
|
||||
function TableBuilderColumnMixin:ConstructCells(templateType, template, ...)
|
||||
local cells = self.table:ConstructCells(templateType, template);
|
||||
self.cells = cells;
|
||||
for k, cell in pairs(cells) do
|
||||
if cell.Init then
|
||||
cell:Init(...);
|
||||
end
|
||||
cell:Show();
|
||||
end
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:GetCellByRowIndex(rowIndex)
|
||||
return self.cells[rowIndex];
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:GetFillCoefficient()
|
||||
return self.fillCoefficient;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:SetFillCoefficient(fillCoefficient)
|
||||
self.fillCoefficient = fillCoefficient;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:GetPadding()
|
||||
return self.padding;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:SetPadding(padding)
|
||||
self.padding = padding;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:GetCellPadding()
|
||||
return self.leftCellPadding or 0, self.rightCellPadding or 0;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:SetCellPadding(leftCellPadding, rightCellPadding)
|
||||
self.leftCellPadding = leftCellPadding;
|
||||
self.rightCellPadding = rightCellPadding;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:GetHeaderFrame()
|
||||
return self.headerFrame;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:SetHeaderFrame(headerFrame)
|
||||
self.headerFrame = headerFrame;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:GetWidthConstraints()
|
||||
return self.widthConstraints;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:GetFixedWidth()
|
||||
return self.fixedWidth;
|
||||
end
|
||||
|
||||
-- A header frame for the column is expected to be constructed or assigned prior to calling this.
|
||||
-- See ConstructHeader() or SetHeaderFrame().
|
||||
function TableBuilderColumnMixin:ConstrainToHeader(padding)
|
||||
local header = self:GetHeaderFrame();
|
||||
assert(header, "ConstrainToHeader() called with a nil header frame. Use ConstructHeader() or assign one with SetHeaderFrame(), or use SetFixedConstraints to have a headerless column.");
|
||||
self:SetFixedConstraints(header:GetWidth(), padding or 0);
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:SetFixedConstraints(fixedWidth, padding)
|
||||
self.widthConstraints = ColumnWidthConstraints.Fixed;
|
||||
self.fixedWidth = fixedWidth;
|
||||
self:SetFillCoefficient(0);
|
||||
self:SetPadding(padding or 0);
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:SetFillConstraints(fillCoefficient, padding)
|
||||
self.widthConstraints = ColumnWidthConstraints.Fill;
|
||||
self.fixedWidth = 0;
|
||||
self:SetFillCoefficient(fillCoefficient);
|
||||
self:SetPadding(padding or 0);
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:SetCalculatedWidth(calculatedWidth)
|
||||
self.calculatedWidth = calculatedWidth;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:GetCalculatedWidth()
|
||||
return self.calculatedWidth;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:GetCellWidth()
|
||||
local leftCellPadding, rightCellPadding = self:GetCellPadding();
|
||||
return (self.calculatedWidth - leftCellPadding) - rightCellPadding;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:GetFullWidth()
|
||||
return self:GetCalculatedWidth() + self:GetPadding();
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:SetDisplayUnderPreviousHeader(displayUnderPreviousHeader)
|
||||
self.displayUnderPreviousHeader = displayUnderPreviousHeader;
|
||||
end
|
||||
|
||||
function TableBuilderColumnMixin:GetDisplayUnderPreviousHeader()
|
||||
return self.displayUnderPreviousHeader;
|
||||
end
|
||||
|
||||
-- Constructs a table of frames within an existing set of row frames. These row frames could originate from
|
||||
-- a hybrid scroll frame or statically fixed set. To populate the table, assign a data provider (CAPI or lua function)
|
||||
-- that can retrieve an object by index (number).
|
||||
TableBuilderMixin = {};
|
||||
function TableBuilderMixin:Init(rows)
|
||||
self.columns = {};
|
||||
self.leftMargin = 0;
|
||||
self.rightMargin = 0;
|
||||
self.columnHeaderOverlap = 0;
|
||||
self.tableWidth = 0;
|
||||
self.headerPoolCollection = CreateFramePoolCollection();
|
||||
self:SetRows(rows);
|
||||
end
|
||||
|
||||
function TableBuilderMixin:GetDataProvider()
|
||||
return self.dataProvider;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:SetDataProvider(dataProvider)
|
||||
self.dataProvider = dataProvider;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:GetDataProviderData(dataIndex)
|
||||
local dataProvider = self:GetDataProvider();
|
||||
return dataProvider and dataProvider(dataIndex) or nil;
|
||||
end
|
||||
|
||||
-- Controls the margins of the left-most and right-most columns within the table.
|
||||
function TableBuilderMixin:SetTableMargins(leftMargin, rightMargin)
|
||||
rightMargin = rightMargin or leftMargin; -- Use leftMargin as the default for both.
|
||||
self.leftMargin = leftMargin;
|
||||
self.rightMargin = rightMargin;
|
||||
end
|
||||
|
||||
-- Column headers overlap to make a consistent display.
|
||||
function TableBuilderMixin:SetColumnHeaderOverlap(columnHeaderOverlap)
|
||||
self.columnHeaderOverlap = columnHeaderOverlap;
|
||||
end
|
||||
|
||||
-- Can be used to set the table width, particularly if no header frames are involved.
|
||||
function TableBuilderMixin:SetTableWidth(tableWidth)
|
||||
self.tableWidth = tableWidth;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:GetTableWidth()
|
||||
return self.tableWidth;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:GetTableMargins()
|
||||
return self.leftMargin, self.rightMargin;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:GetColumnHeaderOverlap()
|
||||
return self.columnHeaderOverlap;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:GetColumns()
|
||||
return self.columns;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:GetHeaderContainer()
|
||||
return self.headerContainer;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:SetHeaderContainer(headerContainer)
|
||||
assert(headerContainer, "SetHeaderContainer() with a nil header container. Use ConstructHeader() or assign one with SetHeaderFrame(), or use SetFixedConstraints to have a headerless column.");
|
||||
self.headerContainer = headerContainer;
|
||||
self:SetTableWidth(headerContainer:GetWidth());
|
||||
end
|
||||
|
||||
function TableBuilderMixin:ReleaseRowPools()
|
||||
local rows = self.rows;
|
||||
if rows then
|
||||
for k, row in pairs(rows) do
|
||||
local poolCollection = row.poolCollection;
|
||||
if poolCollection then
|
||||
poolCollection:ReleaseAll();
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TableBuilderMixin:SetRows(rows)
|
||||
-- Release any previous rows, though I can't imagine a case where the rows
|
||||
-- are being exchanged.
|
||||
self:ReleaseRowPools();
|
||||
|
||||
self.rows = rows;
|
||||
for k, row in pairs(rows) do
|
||||
if not row.poolCollection then
|
||||
row.poolCollection = CreateFramePoolCollection();
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TableBuilderMixin:GetHeaderPoolCollection()
|
||||
return self.headerPoolCollection;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:GetRows()
|
||||
return self.rows;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:ConstructHeader(templateType, template)
|
||||
local headerContainer = self:GetHeaderContainer();
|
||||
assert(headerContainer ~= nil, "A header container must be set with TableBuilderMixin:SetHeaderContainer before adding column headers.")
|
||||
local headerPoolCollection = self:GetHeaderPoolCollection();
|
||||
local pool = headerPoolCollection:GetOrCreatePool(templateType, headerContainer, template);
|
||||
return pool:Acquire(template);
|
||||
end
|
||||
|
||||
function TableBuilderMixin:ConstructCells(templateType, template)
|
||||
local cells = {};
|
||||
for k, row in pairs(self:GetRows()) do
|
||||
local pool = row.poolCollection:GetOrCreatePool(templateType, row, template);
|
||||
local cell = pool:Acquire(template);
|
||||
tinsert(cells, cell);
|
||||
end
|
||||
return cells;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:Arrange()
|
||||
local columns = self:GetColumns();
|
||||
if columns and #columns > 0 then
|
||||
self:CalculateColumnSpacing();
|
||||
self:ArrangeHeaders();
|
||||
self:ArrangeCells();
|
||||
end
|
||||
end
|
||||
|
||||
function TableBuilderMixin:Reset()
|
||||
self.columns = {};
|
||||
self:GetHeaderPoolCollection():ReleaseAll();
|
||||
self:ReleaseRowPools();
|
||||
end
|
||||
|
||||
function TableBuilderMixin:Populate(offset, count)
|
||||
local dataProvider = self:GetDataProvider();
|
||||
local columns = self:GetColumns();
|
||||
for rowIndex = 1, count do
|
||||
local dataIndex = rowIndex + offset;
|
||||
local rowData = dataProvider(dataIndex);
|
||||
if not rowData then
|
||||
break;
|
||||
end
|
||||
|
||||
local row = self:GetRowByIndex(rowIndex);
|
||||
if row then
|
||||
-- Data is assigned to the rows and elements so they can
|
||||
-- access it later in tooltips.
|
||||
row.rowData = rowData;
|
||||
if row.Populate then
|
||||
row:Populate(rowData, dataIndex);
|
||||
end
|
||||
|
||||
for columnIndex, p in ipairs(columns) do
|
||||
local cell = self:GetCellByIndex(rowIndex, columnIndex);
|
||||
if cell.Populate then
|
||||
cell.rowData = rowData;
|
||||
cell:Populate(rowData, dataIndex);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TableBuilderMixin:GetCellByIndex(rowIndex, index)
|
||||
local row = self:GetRowByIndex(rowIndex);
|
||||
return row.cells[index];
|
||||
end
|
||||
|
||||
function TableBuilderMixin:GetRowByIndex(rowIndex)
|
||||
return self.rows[rowIndex];
|
||||
end
|
||||
|
||||
function TableBuilderMixin:AddColumn()
|
||||
local column = CreateAndInitFromMixin(TableBuilderColumnMixin, self);
|
||||
tinsert(self.columns, column);
|
||||
return column;
|
||||
end
|
||||
|
||||
function TableBuilderMixin:CalculateColumnSpacing()
|
||||
-- The arrangement of frames is daisy-chained left to right. The margin on the left side
|
||||
-- is created by adding the margin to it's anchor offset, and the margin on the right side
|
||||
-- is created by subtracting space from the remaining fill space.
|
||||
local columns = self:GetColumns();
|
||||
local paddingTotal = 0;
|
||||
local fillCoefficientTotal = 0;
|
||||
local fixedWidthTotal = 0;
|
||||
for columnIndex, column in ipairs(columns) do
|
||||
if column:GetWidthConstraints() == ColumnWidthConstraints.Fill then
|
||||
fillCoefficientTotal = fillCoefficientTotal + column:GetFillCoefficient();
|
||||
else
|
||||
fixedWidthTotal = fixedWidthTotal + column:GetFixedWidth();
|
||||
end
|
||||
|
||||
paddingTotal = paddingTotal + column:GetPadding();
|
||||
end
|
||||
|
||||
local tableWidth = self:GetTableWidth();
|
||||
local leftMargin, rightMargin = self:GetTableMargins();
|
||||
local fillWidthTotal = tableWidth - paddingTotal - (leftMargin + rightMargin) - fixedWidthTotal;
|
||||
for k, column in pairs(columns) do
|
||||
if fillCoefficientTotal > 0 and column:GetWidthConstraints() == ColumnWidthConstraints.Fill then
|
||||
local fillRatio = column:GetFillCoefficient() / fillCoefficientTotal;
|
||||
local width = fillRatio * fillWidthTotal;
|
||||
column:SetCalculatedWidth(width);
|
||||
else
|
||||
local width = column:GetFixedWidth();
|
||||
column:SetCalculatedWidth(width);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TableBuilderMixin:ArrangeHorizontally(frame, relativeTo, width, pointTop, pointRelativeTop, pointBottom, pointRelativeBottom, xOffset)
|
||||
frame:SetPoint(pointTop, relativeTo, pointRelativeTop, xOffset, 0);
|
||||
frame:SetPoint(pointBottom, relativeTo, pointRelativeBottom, xOffset, 0);
|
||||
frame:SetWidth(width);
|
||||
end
|
||||
|
||||
function TableBuilderMixin:ArrangeHeaders()
|
||||
local headerOverlap = self:GetColumnHeaderOverlap();
|
||||
local leftMargin, rightMargin = self:GetTableMargins();
|
||||
local columns = self:GetColumns();
|
||||
|
||||
-- Any trailing columns without headers should add to the width of the last column with a header.
|
||||
local numColumns = #columns;
|
||||
local lastActiveHeaderIndex = numColumns;
|
||||
local trailingWidth = 0;
|
||||
for i, column in ipairs(columns) do
|
||||
if column:GetHeaderFrame() then
|
||||
trailingWidth = 0;
|
||||
lastActiveHeaderIndex = i;
|
||||
else
|
||||
trailingWidth = trailingWidth + column:GetFullWidth();
|
||||
end
|
||||
end
|
||||
|
||||
local previousHeader = nil;
|
||||
local accumulatedWidth = 0;
|
||||
local columnIndex = 1;
|
||||
|
||||
while columnIndex <= numColumns do
|
||||
local column = columns[columnIndex];
|
||||
accumulatedWidth = accumulatedWidth + column:GetCalculatedWidth();
|
||||
|
||||
local isLastIndex = columnIndex == lastActiveHeaderIndex;
|
||||
local header = column:GetHeaderFrame();
|
||||
if header then
|
||||
if isLastIndex then
|
||||
accumulatedWidth = accumulatedWidth + trailingWidth;
|
||||
else
|
||||
for j = columnIndex + 1, #columns do
|
||||
local nextColumn = columns[j];
|
||||
if nextColumn:GetDisplayUnderPreviousHeader() then
|
||||
columnIndex = columnIndex + 1;
|
||||
accumulatedWidth = accumulatedWidth + nextColumn:GetFullWidth();
|
||||
else
|
||||
break;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if previousHeader == nil then
|
||||
self:ArrangeHorizontally(header, header:GetParent(), accumulatedWidth, "TOPLEFT", "TOPLEFT", "BOTTOMLEFT", "BOTTOMLEFT", leftMargin);
|
||||
else
|
||||
self:ArrangeHorizontally(header, previousHeader, accumulatedWidth + headerOverlap, "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT", column:GetPadding() - headerOverlap);
|
||||
end
|
||||
|
||||
accumulatedWidth = 0;
|
||||
previousHeader = header;
|
||||
end
|
||||
|
||||
if isLastIndex then
|
||||
break;
|
||||
end
|
||||
|
||||
columnIndex = columnIndex + 1;
|
||||
end
|
||||
end
|
||||
|
||||
function TableBuilderMixin:ArrangeCells()
|
||||
local columns = self:GetColumns();
|
||||
local leftMargin, rightMargin = self:GetTableMargins();
|
||||
for rowIndex, row in ipairs(self:GetRows()) do
|
||||
local cells = {};
|
||||
local height = row:GetHeight();
|
||||
|
||||
local column = columns[1];
|
||||
local cell = column:GetCellByRowIndex(rowIndex);
|
||||
tinsert(cells, cell);
|
||||
cell:SetHeight(height);
|
||||
local leftCellPadding, rightCellPadding = column:GetCellPadding();
|
||||
|
||||
self:ArrangeHorizontally(cell, row, column:GetCellWidth(), "TOPLEFT", "TOPLEFT", "BOTTOMLEFT", "BOTTOMLEFT", leftMargin + leftCellPadding);
|
||||
|
||||
local previousCell = cell;
|
||||
local previousRightCellPadding = rightCellPadding;
|
||||
for columnIndex = 2, #columns do
|
||||
column = columns[columnIndex];
|
||||
cell = column:GetCellByRowIndex(rowIndex);
|
||||
tinsert(cells, cell);
|
||||
cell:SetHeight(height);
|
||||
leftCellPadding, rightCellPadding = column:GetCellPadding();
|
||||
|
||||
self:ArrangeHorizontally(cell, previousCell, column:GetCellWidth(), "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT", column:GetPadding() + leftCellPadding + previousRightCellPadding);
|
||||
previousCell = cell;
|
||||
previousRightCellPadding = rightCellPadding;
|
||||
end
|
||||
|
||||
row.cells = cells;
|
||||
end
|
||||
end
|
||||
|
||||
-- ... are additional mixins
|
||||
function CreateTableBuilder(rows, ...)
|
||||
local tableBuilder = CreateAndInitFromMixin(TableBuilderMixin, rows);
|
||||
Mixin(tableBuilder, ...);
|
||||
return tableBuilder;
|
||||
end
|
||||
@@ -0,0 +1,697 @@
|
||||
local tRemove = table.remove;
|
||||
local tInsert = table.insert;
|
||||
local tWipe = table.wipe;
|
||||
|
||||
TableUtil = {};
|
||||
|
||||
TableUtil.Constants =
|
||||
{
|
||||
AssociativePriorityTable = true,
|
||||
ArraylikePriorityTable = false,
|
||||
IsIndexTable = true,
|
||||
};
|
||||
|
||||
function ipairs_reverse(table)
|
||||
local function Enumerator(table, index)
|
||||
index = index - 1;
|
||||
local value = table[index];
|
||||
if value ~= nil then
|
||||
return index, value;
|
||||
end
|
||||
end
|
||||
return Enumerator, table, #table + 1;
|
||||
end
|
||||
|
||||
function CreateTableEnumerator(tbl, minIndex, maxIndex)
|
||||
minIndex = minIndex and (minIndex - 1) or 0;
|
||||
maxIndex = maxIndex or math.huge;
|
||||
|
||||
local function Enumerator(tbl, index)
|
||||
index = index + 1;
|
||||
if index <= maxIndex then
|
||||
local value = tbl[index];
|
||||
if value ~= nil then
|
||||
return index, value;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Enumerator, tbl, minIndex;
|
||||
end
|
||||
|
||||
function CreateTableReverseEnumerator(tbl, minIndex, maxIndex)
|
||||
minIndex = minIndex or 1;
|
||||
maxIndex = (maxIndex or #tbl) + 1;
|
||||
|
||||
local function Enumerator(tbl, index)
|
||||
index = index - 1;
|
||||
if index >= minIndex then
|
||||
local value = tbl[index];
|
||||
if value ~= nil then
|
||||
return index, value;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Enumerator, tbl, maxIndex;
|
||||
end
|
||||
|
||||
function tCount(t)
|
||||
if type(t) ~= "table" then
|
||||
return 0
|
||||
end
|
||||
local size = 0
|
||||
for _ in pairs(t) do
|
||||
size = size + 1
|
||||
end
|
||||
return size
|
||||
end
|
||||
|
||||
function tDeleteItem(tbl, item)
|
||||
local size = #tbl;
|
||||
local index = size;
|
||||
while index > 0 do
|
||||
if item == tbl[index] then
|
||||
tRemove(tbl, index);
|
||||
end
|
||||
index = index - 1;
|
||||
end
|
||||
return size - #tbl;
|
||||
end
|
||||
|
||||
function tIndexOf(tbl, item)
|
||||
for i, v in ipairs(tbl) do
|
||||
if item == v then
|
||||
return i;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function tContains(tbl, item)
|
||||
local index = 1;
|
||||
while tbl[index] do
|
||||
if ( item == tbl[index] ) then
|
||||
return 1;
|
||||
end
|
||||
index = index + 1;
|
||||
end
|
||||
return nil;
|
||||
end
|
||||
|
||||
function tContainsValue(tbl, item)
|
||||
for k, v in pairs(tbl) do
|
||||
if item == v then
|
||||
return true;
|
||||
end
|
||||
end
|
||||
return false;
|
||||
end
|
||||
|
||||
function TableUtil.ContainsAllKeys(lhsTable, rhsTable)
|
||||
for key, _ in pairs(lhsTable) do
|
||||
if rhsTable[key] == nil then
|
||||
return false;
|
||||
end
|
||||
end
|
||||
-- Check for any keys that are in rhsTable and not lhsTable.
|
||||
for key, _ in pairs(rhsTable) do
|
||||
if lhsTable[key] == nil then
|
||||
return false;
|
||||
end
|
||||
end
|
||||
return true;
|
||||
end
|
||||
|
||||
function TableUtil.CompareValuesAsKeys(lhsTable, rhsTable, valueToKeyOp)
|
||||
local lhsKeys = CopyTransformedValuesAsKeys(lhsTable, valueToKeyOp);
|
||||
local rhsKeys = CopyTransformedValuesAsKeys(rhsTable, valueToKeyOp);
|
||||
return TableUtil.ContainsAllKeys(lhsKeys, rhsKeys)
|
||||
end
|
||||
|
||||
-- This is a deep compare on the values of the table (based on depth) but not a deep comparison
|
||||
-- of the keys, as this would be an expensive check and won't be necessary in most cases.
|
||||
function tCompare(lhsTable, rhsTable, depth)
|
||||
depth = depth or 1;
|
||||
for key, value in pairs(lhsTable) do
|
||||
if type(value) == "table" then
|
||||
local rhsValue = rhsTable[key];
|
||||
if type(rhsValue) ~= "table" then
|
||||
return false;
|
||||
end
|
||||
if depth > 1 then
|
||||
if not tCompare(value, rhsValue, depth - 1) then
|
||||
return false;
|
||||
end
|
||||
end
|
||||
elseif value ~= rhsTable[key] then
|
||||
return false;
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for any keys that are in rhsTable and not lhsTable.
|
||||
for key, value in pairs(rhsTable) do
|
||||
if lhsTable[key] == nil then
|
||||
return false;
|
||||
end
|
||||
end
|
||||
|
||||
return true;
|
||||
end
|
||||
|
||||
function tInvert(tbl)
|
||||
local inverted = {};
|
||||
for k, v in pairs(tbl) do
|
||||
inverted[v] = k;
|
||||
end
|
||||
return inverted;
|
||||
end
|
||||
|
||||
function TableUtil.TrySet(tbl, key)
|
||||
if not tbl[key] then
|
||||
tbl[key] = true;
|
||||
return true;
|
||||
end
|
||||
return false;
|
||||
end
|
||||
|
||||
function TableUtil.CopyUnique(tbl, isIndexTable)
|
||||
local found = {};
|
||||
local function FilterPredicate(value)
|
||||
return TableUtil.TrySet(found, value);
|
||||
end
|
||||
|
||||
return tFilter(tbl, FilterPredicate, isIndexTable);
|
||||
end
|
||||
|
||||
function TableUtil.CopyUniqueByPredicate(tbl, isIndexTable, unaryPredicate)
|
||||
local found = {};
|
||||
local function FilterPredicate(value)
|
||||
return TableUtil.TrySet(found, unaryPredicate(value));
|
||||
end
|
||||
|
||||
return tFilter(tbl, FilterPredicate, isIndexTable);
|
||||
end
|
||||
|
||||
function tFilter(tbl, pred, isIndexTable)
|
||||
local out = {};
|
||||
|
||||
if (isIndexTable) then
|
||||
local currentIndex = 1;
|
||||
for i, v in ipairs(tbl) do
|
||||
if (pred(v)) then
|
||||
out[currentIndex] = v;
|
||||
currentIndex = currentIndex + 1;
|
||||
end
|
||||
end
|
||||
else
|
||||
for k, v in pairs(tbl) do
|
||||
if (pred(v)) then
|
||||
out[k] = v;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return out;
|
||||
end
|
||||
|
||||
function tAppendAll(table, addedArray)
|
||||
for i, element in ipairs(addedArray) do
|
||||
tinsert(table, element);
|
||||
end
|
||||
end
|
||||
|
||||
function tInsertUnique(tbl, item)
|
||||
if not tContains(tbl, item) then
|
||||
table.insert(tbl, item);
|
||||
return #tbl;
|
||||
end
|
||||
return nil;
|
||||
end
|
||||
|
||||
function tUnorderedRemove(tbl, index)
|
||||
if index ~= #tbl then
|
||||
tbl[index] = tbl[#tbl];
|
||||
end
|
||||
|
||||
tRemove(tbl);
|
||||
end
|
||||
|
||||
function CopyTable(settings, shallow)
|
||||
local copy = {};
|
||||
for k, v in pairs(settings) do
|
||||
if type(v) == "table" and not shallow then
|
||||
copy[k] = CopyTable(v);
|
||||
else
|
||||
copy[k] = v;
|
||||
end
|
||||
end
|
||||
return copy;
|
||||
end
|
||||
|
||||
function MergeTable(destination, source)
|
||||
for k, v in pairs(source) do
|
||||
destination[k] = v;
|
||||
end
|
||||
end
|
||||
|
||||
-- Useful if there are external references to a table but we want to set
|
||||
-- that table's key-value pairs to be exactly the same as another table's key-value pairs.
|
||||
function SetTablePairsToTable(destination, source)
|
||||
tWipe(destination);
|
||||
MergeTable(destination, source);
|
||||
end
|
||||
|
||||
function Accumulate(tbl)
|
||||
local count = 0;
|
||||
for k, v in pairs(tbl) do
|
||||
count = count + v;
|
||||
end
|
||||
return count;
|
||||
end
|
||||
|
||||
function AccumulateOp(tbl, op)
|
||||
local count = 0;
|
||||
for k, v in pairs(tbl) do
|
||||
count = count + op(v);
|
||||
end
|
||||
return count;
|
||||
end
|
||||
|
||||
function TableUtil.Execute(tbl, op)
|
||||
for k, v in pairs(tbl) do
|
||||
op(v);
|
||||
end
|
||||
end
|
||||
|
||||
function TableUtil.ExecuteUntil(tbl, op)
|
||||
for k, v in pairs(tbl) do
|
||||
local operationResult = op(v);
|
||||
if operationResult then
|
||||
return operationResult;
|
||||
end
|
||||
end
|
||||
|
||||
return nil;
|
||||
end
|
||||
|
||||
function TableUtil.Transform(tbl, op)
|
||||
local result = {};
|
||||
for k, v in pairs(tbl) do
|
||||
table.insert(result, op(v));
|
||||
end
|
||||
return result;
|
||||
end
|
||||
|
||||
function ContainsIf(tbl, pred)
|
||||
for k, v in pairs(tbl) do
|
||||
if (pred(v)) then
|
||||
return true;
|
||||
end
|
||||
end
|
||||
|
||||
return false;
|
||||
end
|
||||
|
||||
function FindInTableIf(tbl, pred)
|
||||
for k, v in pairs(tbl) do
|
||||
if (pred(v)) then
|
||||
return k, v;
|
||||
end
|
||||
end
|
||||
|
||||
return nil;
|
||||
end
|
||||
|
||||
function FindValueInTableIf(tbl, pred)
|
||||
local _, value = FindInTableIf(tbl, pred);
|
||||
return value;
|
||||
end
|
||||
|
||||
local function FindSortedIndexImplementation(tbl, searchComparison, startIndex, rangeStart, rangeEnd)
|
||||
local comparisonResult = searchComparison(tbl[startIndex]);
|
||||
if comparisonResult == 0 then
|
||||
return startIndex;
|
||||
end
|
||||
|
||||
if comparisonResult > 0 then
|
||||
if startIndex >= rangeEnd then
|
||||
return startIndex + 1;
|
||||
end
|
||||
|
||||
rangeStart = startIndex + 1;
|
||||
return FindSortedIndexImplementation(tbl, searchComparison, startIndex + math.ceil((rangeEnd - startIndex) / 2), rangeStart, rangeEnd);
|
||||
end
|
||||
|
||||
-- comparisonResult < 0
|
||||
if startIndex <= rangeStart then
|
||||
return startIndex;
|
||||
end
|
||||
|
||||
rangeEnd = startIndex - 1;
|
||||
return FindSortedIndexImplementation(tbl, searchComparison, math.floor(startIndex / 2), rangeStart, rangeEnd);
|
||||
end
|
||||
|
||||
function FindSortedIndex(tbl, searchComparison)
|
||||
local numTable = #tbl;
|
||||
local startingIndex = math.ceil(numTable / 2);
|
||||
if startingIndex == 0 then
|
||||
return 1;
|
||||
end
|
||||
|
||||
return FindSortedIndexImplementation(tbl, searchComparison, startingIndex, 1, numTable);
|
||||
end
|
||||
|
||||
function TableIsEmpty(tbl)
|
||||
return next(tbl) == nil;
|
||||
end
|
||||
|
||||
function TableHasAnyEntries(tbl)
|
||||
return next(tbl) ~= nil;
|
||||
end
|
||||
|
||||
function CopyValuesAsKeys(tbl)
|
||||
local output = {};
|
||||
for k, v in ipairs(tbl) do
|
||||
output[v] = v;
|
||||
end
|
||||
return output;
|
||||
end
|
||||
|
||||
function CopyTransformedValuesAsKeys(tbl, transformOp)
|
||||
local output = {};
|
||||
for _, v in ipairs(tbl) do
|
||||
output[transformOp(v)] = v;
|
||||
end
|
||||
return output;
|
||||
end
|
||||
|
||||
-- Addresses the problem where nil values within a varargs list are not preserved when constructing
|
||||
-- a table, resulting a table with a smaller size than expected. Should be paired with a call to
|
||||
-- SafeUnpack when unpacking the table.
|
||||
function SafePack(...)
|
||||
local tbl = { ... };
|
||||
tbl.n = select("#", ...);
|
||||
return tbl;
|
||||
end
|
||||
|
||||
-- Upacks a table that was constructed using SafePack.
|
||||
function SafeUnpack(tbl, startIndex)
|
||||
return unpack(tbl, startIndex or 1, tbl.n);
|
||||
end
|
||||
|
||||
-- Returns the length of a table, accounting for the possibility of a table constructed using SafePack.
|
||||
function SafeLength(tbl)
|
||||
if not tbl then
|
||||
return 0;
|
||||
end
|
||||
|
||||
local operatorCount = #tbl;
|
||||
local safePackCount = tbl.n;
|
||||
|
||||
if safePackCount and operatorCount ~= safePackCount then
|
||||
return safePackCount;
|
||||
end
|
||||
|
||||
return operatorCount;
|
||||
end
|
||||
|
||||
function GetOrCreateTableEntry(table, key, defaultValue)
|
||||
local currentValue = table[key];
|
||||
local isNewValue = (currentValue == nil);
|
||||
if isNewValue then
|
||||
if defaultValue ~= nil then
|
||||
currentValue = defaultValue;
|
||||
else
|
||||
currentValue = {};
|
||||
end
|
||||
table[key] = currentValue;
|
||||
end
|
||||
|
||||
return currentValue, isNewValue;
|
||||
end
|
||||
|
||||
function GetOrCreateTableEntryByCallback(table, key, callback)
|
||||
local currentValue = table[key];
|
||||
local isNewValue = (currentValue == nil);
|
||||
if isNewValue then
|
||||
currentValue = callback(key);
|
||||
table[key] = currentValue;
|
||||
end
|
||||
|
||||
return currentValue, isNewValue;
|
||||
end
|
||||
|
||||
function GetRandomArrayEntry(array)
|
||||
return array[math.random(1, #array)];
|
||||
end
|
||||
|
||||
function GetRandomTableValue(tbl)
|
||||
local value;
|
||||
local n = 0;
|
||||
for k, v in pairs(tbl) do
|
||||
n = n + 1;
|
||||
local r = math.random();
|
||||
if r <= (1 / n) then
|
||||
value = v;
|
||||
end
|
||||
end
|
||||
return value;
|
||||
end
|
||||
|
||||
function GetKeysArray(tbl)
|
||||
local keysArray = {};
|
||||
for key in pairs(tbl) do
|
||||
tInsert(keysArray, key);
|
||||
end
|
||||
|
||||
return keysArray;
|
||||
end
|
||||
|
||||
function GetValuesArray(tbl)
|
||||
local valuesArray = {};
|
||||
for key, value in pairs(tbl) do
|
||||
tInsert(valuesArray, value);
|
||||
end
|
||||
|
||||
return valuesArray;
|
||||
end
|
||||
|
||||
function GetPairsArray(tbl)
|
||||
local pairsArray = {};
|
||||
for key, value in pairs(tbl) do
|
||||
tInsert(pairsArray, { key = key, value = value, });
|
||||
end
|
||||
|
||||
return pairsArray;
|
||||
end
|
||||
|
||||
function SwapTableEntries(lhsTable, rhsTable, key)
|
||||
local lhsValue = lhsTable[key];
|
||||
lhsTable[key] = rhsTable[key];
|
||||
rhsTable[key] = lhsValue;
|
||||
end
|
||||
|
||||
function GetKeysArraySortedByValue(tbl)
|
||||
local keysArray = GetKeysArray(tbl);
|
||||
|
||||
table.sort(keysArray, function(a, b)
|
||||
return tbl[a] < tbl[b];
|
||||
end);
|
||||
|
||||
return keysArray;
|
||||
end
|
||||
|
||||
function TableUtil.OperateOnKeys(tbl, operation)
|
||||
for key, value in pairs(tbl) do
|
||||
operation(key);
|
||||
end
|
||||
end
|
||||
|
||||
function TableUtil.GetTableValueListFromEnumeration(tableKey, ...)
|
||||
local values = {};
|
||||
for enumerationKey, tbl in ... do
|
||||
table.insert(values, tbl[tableKey]);
|
||||
end
|
||||
|
||||
return values;
|
||||
end
|
||||
|
||||
function TableUtil.GetHighestNumericalValueInTable(table)
|
||||
local highestValue = nil;
|
||||
for key, value in pairs(table) do
|
||||
if type(value) == "number" and (not highestValue or value > highestValue) then
|
||||
highestValue = value;
|
||||
end
|
||||
end
|
||||
return highestValue;
|
||||
end
|
||||
|
||||
--[[
|
||||
This utility creates and returns a table of elements that are sorted by value "priority".
|
||||
|
||||
Arguments:
|
||||
comparator - comparator(A, B) returns whether A has higher priority than B
|
||||
isAssociative - whether the table should be associative (true) or array-like (false). Only values have priorities for associative tables, not keys.
|
||||
|
||||
Return usage:
|
||||
t[k]/t:Get(k) - returns the value stored with key/index k
|
||||
t[k] = v (Associative only) - stores the value v at key k, sorting accordingly
|
||||
t:Insert(v) (Array-like only) - inserts the value v, sorting accordingly
|
||||
t:Remove(k) - removes the element at key/index k
|
||||
t:Iterate(cb) - calls function cb on each key/index value pair in sorted priority order. !!WARNING!! This must be used instead of pairs(t)/ipairs(t)
|
||||
t:GetTop() - returns the highest priority element
|
||||
t:Pop() - returns and removes the highest priority element
|
||||
t:GetBottom() - returns the lowest priority element
|
||||
t:Size() - returns the number of stored elements. !!WARNING!! This must be used instead of #t
|
||||
t:Clear() - removes all elements
|
||||
--]]
|
||||
function TableUtil.CreatePriorityTable(comparator, isAssociative)
|
||||
local sortedArray = {};
|
||||
|
||||
local ShiftPositionMap;
|
||||
local keyToPosMap;
|
||||
if isAssociative then
|
||||
keyToPosMap = {};
|
||||
|
||||
ShiftPositionMap = function(position, shiftUp)
|
||||
local mod = shiftUp and 1 or -1;
|
||||
for k, p in pairs(keyToPosMap) do
|
||||
if p >= position then
|
||||
keyToPosMap[k] = p + mod;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function SortedInsert(k, v)
|
||||
if isAssociative then
|
||||
local prevPos = keyToPosMap[k];
|
||||
if prevPos then
|
||||
tRemove(sortedArray, prevPos);
|
||||
local shiftUp = false;
|
||||
ShiftPositionMap(prevPos, shiftUp);
|
||||
end
|
||||
end
|
||||
|
||||
local top = (#sortedArray > 0) and (#sortedArray + 1) or 1;
|
||||
local bottom = 1;
|
||||
while top ~= bottom do
|
||||
local mid = math.floor((top - bottom) / 2) + bottom;
|
||||
if not comparator(v, sortedArray[mid]) then
|
||||
bottom = mid + 1;
|
||||
else
|
||||
top = mid;
|
||||
end
|
||||
end
|
||||
local idx = bottom;
|
||||
tInsert(sortedArray, idx, v);
|
||||
|
||||
if isAssociative then
|
||||
local shiftUp = true;
|
||||
ShiftPositionMap(idx, shiftUp);
|
||||
keyToPosMap[k] = idx;
|
||||
end
|
||||
end
|
||||
|
||||
local t = {};
|
||||
|
||||
function t:Get(k)
|
||||
local key = isAssociative and keyToPosMap[k] or k;
|
||||
return sortedArray[key];
|
||||
end
|
||||
|
||||
if not isAssociative then
|
||||
function t:Insert(v)
|
||||
SortedInsert(nil, v)
|
||||
end
|
||||
end
|
||||
|
||||
function t:Remove(k)
|
||||
local key = isAssociative and keyToPosMap[k] or k;
|
||||
tRemove(sortedArray, key);
|
||||
if isAssociative then
|
||||
keyToPosMap[k] = nil;
|
||||
local shiftUp = false;
|
||||
ShiftPositionMap(key, shiftUp);
|
||||
end
|
||||
end
|
||||
|
||||
function t:GetTop()
|
||||
return #sortedArray > 0 and sortedArray[1];
|
||||
end
|
||||
|
||||
function t:GetBottom()
|
||||
return #sortedArray > 0 and sortedArray[#sortedArray];
|
||||
end
|
||||
|
||||
function t:Pop()
|
||||
if t:Size() == 0 then
|
||||
return nil;
|
||||
end
|
||||
|
||||
local top = t:GetTop();
|
||||
local removalKey = isAssociative and tInvert(keyToPosMap)[1] or 1;
|
||||
t:Remove(removalKey);
|
||||
return top;
|
||||
end
|
||||
|
||||
function t:Iterate(callback)
|
||||
local posToKeyMap = isAssociative and tInvert(keyToPosMap) or nil;
|
||||
for pos, v in ipairs(sortedArray) do
|
||||
local key = isAssociative and posToKeyMap[pos] or pos;
|
||||
local done = callback(key, v);
|
||||
if done then
|
||||
return;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function t:Size()
|
||||
return #sortedArray;
|
||||
end
|
||||
|
||||
function t:Clear()
|
||||
sortedArray = {};
|
||||
if isAssociative then
|
||||
keyToPosMap = {};
|
||||
end
|
||||
end
|
||||
|
||||
local mt =
|
||||
{
|
||||
__index = function(t, k)
|
||||
return t:Get(k);
|
||||
end,
|
||||
};
|
||||
if isAssociative then
|
||||
mt.__newindex = function(t, k, v)
|
||||
if v ~= nil then
|
||||
SortedInsert(k, v);
|
||||
else
|
||||
t:Remove(k);
|
||||
end
|
||||
end
|
||||
else
|
||||
mt.__newindex = function(t, k, v)
|
||||
error("Attempted to assign a value to an array-like priority queue index. Use Insert()/Remove() instead.")
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(t, mt);
|
||||
|
||||
return t;
|
||||
end
|
||||
|
||||
function DeepMergeTable(destination, source)
|
||||
for k, v in pairs(source) do
|
||||
if type(v) == "table" then
|
||||
if type(destination[k]) == "table" then
|
||||
DeepMergeTable(destination[k], v)
|
||||
else
|
||||
destination[k] = CopyTable(v)
|
||||
end
|
||||
else
|
||||
destination[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||