修复拾取界面点击无效问题 修复宠物训练界面不显示训练点问题 新增天赋分享到聊天界面 修复飞行界面无法关闭问题 修复术士宠物的显示问题 为天赋界面添加默认数据库支持 框架现在也可自主选择是否启用 玩家框架和目标框架可取消显示3D头像以及透明度修改 背包和银行也添加透明度自定义支持 优化tooltip性能和背包物品显示方式 修复布局模式在ui缩放后不能正常定位的问题 添加硬核模式危险和死亡的工会通报 添加拾取和已拾取的框体 等等
1256 lines
48 KiB
Lua
1256 lines
48 KiB
Lua
--------------------------------------------------------------------------------
|
|
-- Nanami-UI: Trainer UI (TrainerUI.lua)
|
|
-- Replaces ClassTrainerFrame with Nanami-UI styled interface
|
|
--------------------------------------------------------------------------------
|
|
|
|
SFrames = SFrames or {}
|
|
SFrames.TrainerUI = {}
|
|
local TUI = SFrames.TrainerUI
|
|
SFramesDB = SFramesDB or {}
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Theme (Pink Cat-Paw)
|
|
--------------------------------------------------------------------------------
|
|
local T = SFrames.Theme:Extend({
|
|
moneyGold = { 1, 0.84, 0.0 },
|
|
moneySilver = { 0.78, 0.78, 0.78 },
|
|
moneyCopper = { 0.71, 0.43, 0.18 },
|
|
available = { 0.25, 1.0, 0.25 },
|
|
unavailable = { 0.80, 0.20, 0.20 },
|
|
used = { 0.50, 0.50, 0.50 },
|
|
})
|
|
|
|
local QUALITY_COLORS = {
|
|
[0] = { 0.62, 0.62, 0.62 }, [1] = { 1, 1, 1 },
|
|
[2] = { 0.12, 1, 0 }, [3] = { 0.0, 0.44, 0.87 },
|
|
[4] = { 0.64, 0.21, 0.93 }, [5] = { 1, 0.5, 0 },
|
|
}
|
|
|
|
local function ColorToQuality(r, g, b)
|
|
if not r then return nil end
|
|
if r > 0.9 and g > 0.35 and g < 0.65 and b < 0.15 then return 5 end
|
|
if r > 0.5 and r < 0.8 and g < 0.35 and b > 0.8 then return 4 end
|
|
if r < 0.15 and g > 0.3 and g < 0.6 and b > 0.7 then return 3 end
|
|
if r < 0.25 and g > 0.85 and b < 0.15 then return 2 end
|
|
if r > 0.5 and r < 0.75 and g > 0.5 and g < 0.75 and b > 0.5 and b < 0.75 then return 0 end
|
|
return 1
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Layout
|
|
--------------------------------------------------------------------------------
|
|
local FRAME_W = 380
|
|
local FRAME_H = 540
|
|
local HEADER_H = 34
|
|
local SIDE_PAD = 14
|
|
local CONTENT_W = FRAME_W - SIDE_PAD * 2
|
|
local FILTER_H = 28
|
|
local LIST_ROW_H = 32
|
|
local CAT_ROW_H = 20
|
|
local DETAIL_H = 160
|
|
local BOTTOM_H = 52
|
|
local SCROLL_STEP = 40
|
|
local MAX_ROWS = 60
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- State
|
|
--------------------------------------------------------------------------------
|
|
local MainFrame = nil
|
|
local selectedIndex = nil
|
|
local currentFilter = "all"
|
|
local displayList = {}
|
|
local rowButtons = {}
|
|
local collapsedCats = {}
|
|
local function HideBlizzardTrainer()
|
|
if not ClassTrainerFrame then return end
|
|
ClassTrainerFrame:SetScript("OnHide", function() end)
|
|
if ClassTrainerFrame:IsVisible() then
|
|
if HideUIPanel then
|
|
pcall(HideUIPanel, ClassTrainerFrame)
|
|
else
|
|
ClassTrainerFrame:Hide()
|
|
end
|
|
end
|
|
ClassTrainerFrame:SetAlpha(0)
|
|
ClassTrainerFrame:EnableMouse(false)
|
|
ClassTrainerFrame:ClearAllPoints()
|
|
ClassTrainerFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMRIGHT", 2000, 2000)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Helpers
|
|
--------------------------------------------------------------------------------
|
|
local function GetFont()
|
|
if SFrames and SFrames.GetFont then return SFrames:GetFont() end
|
|
return "Fonts\\ARIALN.TTF"
|
|
end
|
|
|
|
local function SetRoundBackdrop(frame)
|
|
frame:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 14,
|
|
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
|
})
|
|
frame:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], T.panelBg[4])
|
|
frame:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4])
|
|
end
|
|
|
|
local function CreateShadow(parent)
|
|
local s = CreateFrame("Frame", nil, parent)
|
|
s:SetPoint("TOPLEFT", parent, "TOPLEFT", -4, 4)
|
|
s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 4, -4)
|
|
s:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 16,
|
|
insets = { left = 4, right = 4, top = 4, bottom = 4 },
|
|
})
|
|
s:SetBackdropColor(0, 0, 0, 0.45)
|
|
s:SetBackdropBorderColor(0, 0, 0, 0.6)
|
|
s:SetFrameLevel(math.max(0, parent:GetFrameLevel() - 1))
|
|
return s
|
|
end
|
|
|
|
local function FormatMoneyText(copper)
|
|
if not copper or copper <= 0 then return "" end
|
|
local g = math.floor(copper / 10000)
|
|
local s = math.floor(math.mod(copper, 10000) / 100)
|
|
local c = math.mod(copper, 100)
|
|
local parts = {}
|
|
if g > 0 then table.insert(parts, "|cffffd700" .. g .. "g|r") end
|
|
if s > 0 then table.insert(parts, "|cffc7c7cf" .. s .. "s|r") end
|
|
if c > 0 then table.insert(parts, "|cffeda55f" .. c .. "c|r") end
|
|
if table.getn(parts) == 0 then return "|cffc7c7cf0c|r" end
|
|
return table.concat(parts, " ")
|
|
end
|
|
|
|
local function SetMoneyFrame(moneyFrame, value)
|
|
if not moneyFrame then return end
|
|
value = tonumber(value) or 0
|
|
if value < 0 then value = 0 end
|
|
if SmallMoneyFrame_SetAmount then
|
|
pcall(SmallMoneyFrame_SetAmount, moneyFrame, value)
|
|
elseif MoneyFrame_Update and moneyFrame.GetName then
|
|
local name = moneyFrame:GetName()
|
|
if name and name ~= "" then
|
|
pcall(MoneyFrame_Update, name, value)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function IsServiceHeader(index)
|
|
local name, _, category = GetTrainerServiceInfo(index)
|
|
if not name then return false end
|
|
local hasIcon = GetTrainerServiceIcon and GetTrainerServiceIcon(index)
|
|
if not hasIcon or hasIcon == "" then return true end
|
|
local ok, cost = pcall(GetTrainerServiceCost, index)
|
|
if not ok then return true end
|
|
if category ~= "available" and category ~= "unavailable" and category ~= "used" then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local scanTip = nil
|
|
|
|
local function GetServiceTooltipInfo(index)
|
|
if not scanTip then
|
|
scanTip = CreateFrame("GameTooltip", "SFramesTrainerScanTip", nil, "GameTooltipTemplate")
|
|
end
|
|
scanTip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
|
scanTip:ClearLines()
|
|
local ok = pcall(scanTip.SetTrainerService, scanTip, index)
|
|
if not ok then return "", "" end
|
|
|
|
local infoLines = {}
|
|
local descLines = {}
|
|
local numLines = scanTip:NumLines()
|
|
local foundDesc = false
|
|
|
|
for i = 2, numLines do
|
|
local leftFS = _G["SFramesTrainerScanTipTextLeft" .. i]
|
|
local rightFS = _G["SFramesTrainerScanTipTextRight" .. i]
|
|
local leftText = leftFS and leftFS:GetText() or ""
|
|
local rightText = rightFS and rightFS:GetText() or ""
|
|
|
|
if leftText == "" and rightText == "" then
|
|
if not foundDesc and table.getn(infoLines) > 0 then
|
|
foundDesc = true
|
|
end
|
|
else
|
|
local line
|
|
if rightText ~= "" and leftText ~= "" then
|
|
line = leftText .. " " .. rightText
|
|
elseif leftText ~= "" then
|
|
line = leftText
|
|
else
|
|
line = rightText
|
|
end
|
|
|
|
local isYellow = leftFS and leftFS.GetTextColor and true
|
|
local r, g, b
|
|
if leftFS and leftFS.GetTextColor then
|
|
r, g, b = leftFS:GetTextColor()
|
|
end
|
|
local isWhiteOrYellow = r and (r > 0.9 and g > 0.75)
|
|
|
|
if not foundDesc and rightText ~= "" then
|
|
table.insert(infoLines, line)
|
|
elseif not foundDesc and not isWhiteOrYellow and string.len(leftText) < 30 then
|
|
table.insert(infoLines, line)
|
|
else
|
|
foundDesc = true
|
|
table.insert(descLines, line)
|
|
end
|
|
end
|
|
end
|
|
scanTip:Hide()
|
|
return table.concat(infoLines, "\n"), table.concat(descLines, "\n")
|
|
end
|
|
|
|
local function GetServiceQuality(index)
|
|
if not scanTip then
|
|
scanTip = CreateFrame("GameTooltip", "SFramesTrainerScanTip", nil, "GameTooltipTemplate")
|
|
end
|
|
scanTip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
|
scanTip:ClearLines()
|
|
local ok = pcall(scanTip.SetTrainerService, scanTip, index)
|
|
if not ok then scanTip:Hide() return nil end
|
|
local firstLine = _G["SFramesTrainerScanTipTextLeft1"]
|
|
if not firstLine or not firstLine.GetTextColor then
|
|
scanTip:Hide()
|
|
return nil
|
|
end
|
|
local r, g, b = firstLine:GetTextColor()
|
|
scanTip:Hide()
|
|
return ColorToQuality(r, g, b)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Filter Button Factory
|
|
--------------------------------------------------------------------------------
|
|
local function CreateFilterBtn(parent, text, w)
|
|
local btn = CreateFrame("Button", nil, parent)
|
|
btn:SetWidth(w or 60)
|
|
btn:SetHeight(20)
|
|
btn:SetBackdrop({
|
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
|
tile = false, tileSize = 0, edgeSize = 1,
|
|
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
|
})
|
|
btn:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
btn:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], 0.5)
|
|
|
|
local fs = btn:CreateFontString(nil, "OVERLAY")
|
|
fs:SetFont(GetFont(), 11, "OUTLINE")
|
|
fs:SetPoint("CENTER", 0, 0)
|
|
fs:SetText(text)
|
|
fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
btn.label = fs
|
|
|
|
btn:SetScript("OnEnter", function()
|
|
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
|
|
this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
|
|
end)
|
|
btn:SetScript("OnLeave", function()
|
|
if this.active then
|
|
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
|
|
this:SetBackdropBorderColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 1)
|
|
else
|
|
this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], 0.5)
|
|
end
|
|
end)
|
|
|
|
function btn:SetActive(flag)
|
|
self.active = flag
|
|
if flag then
|
|
self:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
|
|
self:SetBackdropBorderColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 1)
|
|
self.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3])
|
|
else
|
|
self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
self:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], 0.5)
|
|
self.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
end
|
|
end
|
|
|
|
return btn
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Action Button Factory
|
|
--------------------------------------------------------------------------------
|
|
local function CreateActionBtn(parent, text, w)
|
|
local btn = CreateFrame("Button", nil, parent)
|
|
btn:SetWidth(w or 100)
|
|
btn:SetHeight(28)
|
|
btn:SetBackdrop({
|
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
|
tile = false, tileSize = 0, edgeSize = 1,
|
|
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
|
})
|
|
btn:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
btn:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
|
|
|
|
local fs = btn:CreateFontString(nil, "OVERLAY")
|
|
fs:SetFont(GetFont(), 12, "OUTLINE")
|
|
fs:SetPoint("CENTER", 0, 0)
|
|
fs:SetText(text)
|
|
fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
btn.label = fs
|
|
|
|
btn.disabled = false
|
|
function btn:SetDisabled(flag)
|
|
self.disabled = flag
|
|
if flag then
|
|
self.label:SetTextColor(T.btnDisabledText[1], T.btnDisabledText[2], T.btnDisabledText[3])
|
|
self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], 0.5)
|
|
else
|
|
self.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
end
|
|
end
|
|
|
|
btn:SetScript("OnEnter", function()
|
|
if not this.disabled then
|
|
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
|
|
this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
|
|
this.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3])
|
|
end
|
|
end)
|
|
btn:SetScript("OnLeave", function()
|
|
if not this.disabled then
|
|
this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
|
|
this.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
end
|
|
end)
|
|
|
|
return btn
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Quality cache (lazy per-row instead of bulk tooltip scan)
|
|
--------------------------------------------------------------------------------
|
|
local qualityCache = {}
|
|
|
|
local function GetCachedServiceQuality(index)
|
|
if qualityCache[index] ~= nil then return qualityCache[index] end
|
|
local q = GetServiceQuality(index)
|
|
qualityCache[index] = q or false
|
|
return q
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- List Row Factory (reusable for both headers and services)
|
|
--------------------------------------------------------------------------------
|
|
local function CreateListRow(parent, idx)
|
|
local row = CreateFrame("Button", nil, parent)
|
|
row:SetWidth(CONTENT_W)
|
|
row:SetHeight(LIST_ROW_H)
|
|
|
|
local iconFrame = CreateFrame("Frame", nil, row)
|
|
iconFrame:SetWidth(LIST_ROW_H - 4)
|
|
iconFrame:SetHeight(LIST_ROW_H - 4)
|
|
iconFrame:SetPoint("LEFT", row, "LEFT", 0, 0)
|
|
iconFrame:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 12,
|
|
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
|
})
|
|
iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
|
|
iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
|
row.iconFrame = iconFrame
|
|
|
|
local icon = iconFrame:CreateTexture(nil, "ARTWORK")
|
|
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
|
icon:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", 3, -3)
|
|
icon:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -3, 3)
|
|
row.icon = icon
|
|
|
|
local qualGlow = iconFrame:CreateTexture(nil, "OVERLAY")
|
|
qualGlow:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
|
|
qualGlow:SetBlendMode("ADD")
|
|
qualGlow:SetAlpha(0.7)
|
|
qualGlow:SetWidth((LIST_ROW_H - 4) * 1.8)
|
|
qualGlow:SetHeight((LIST_ROW_H - 4) * 1.8)
|
|
qualGlow:SetPoint("CENTER", iconFrame, "CENTER", 0, 0)
|
|
qualGlow:Hide()
|
|
row.qualGlow = qualGlow
|
|
|
|
local nameFS = row:CreateFontString(nil, "OVERLAY")
|
|
nameFS:SetFont(GetFont(), 12, "OUTLINE")
|
|
nameFS:SetPoint("LEFT", iconFrame, "RIGHT", 6, 2)
|
|
nameFS:SetPoint("RIGHT", row, "RIGHT", -90, 0)
|
|
nameFS:SetJustifyH("LEFT")
|
|
nameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
|
|
row.nameFS = nameFS
|
|
|
|
local subFS = row:CreateFontString(nil, "OVERLAY")
|
|
subFS:SetFont(GetFont(), 10, "OUTLINE")
|
|
subFS:SetPoint("TOPLEFT", nameFS, "BOTTOMLEFT", 0, -1)
|
|
subFS:SetJustifyH("LEFT")
|
|
subFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
row.subFS = subFS
|
|
|
|
local costFS = row:CreateFontString(nil, "OVERLAY")
|
|
costFS:SetFont(GetFont(), 11, "OUTLINE")
|
|
costFS:SetPoint("RIGHT", row, "RIGHT", -4, 0)
|
|
costFS:SetJustifyH("RIGHT")
|
|
costFS:Hide()
|
|
row.costFS = costFS
|
|
|
|
local costMoney = CreateFrame("Frame", "SFTRow" .. idx .. "Money", row, "SmallMoneyFrameTemplate")
|
|
costMoney:SetPoint("RIGHT", row, "RIGHT", -2, 0)
|
|
costMoney:SetWidth(100)
|
|
costMoney:SetHeight(14)
|
|
costMoney:SetFrameLevel(row:GetFrameLevel() + 2)
|
|
costMoney:SetScale(0.85)
|
|
costMoney:UnregisterAllEvents()
|
|
costMoney:SetScript("OnEvent", nil)
|
|
costMoney:SetScript("OnShow", nil)
|
|
costMoney.moneyType = nil
|
|
costMoney.hasPickup = nil
|
|
costMoney.small = 1
|
|
row.costMoney = costMoney
|
|
|
|
local catFS = row:CreateFontString(nil, "OVERLAY")
|
|
catFS:SetFont(GetFont(), 11, "OUTLINE")
|
|
catFS:SetPoint("LEFT", row, "LEFT", 4, 0)
|
|
catFS:SetJustifyH("LEFT")
|
|
catFS:SetTextColor(T.catHeader[1], T.catHeader[2], T.catHeader[3])
|
|
catFS:Hide()
|
|
row.catFS = catFS
|
|
|
|
local catSep = row:CreateTexture(nil, "ARTWORK")
|
|
catSep:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
catSep:SetHeight(1)
|
|
catSep:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
|
|
catSep:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", 0, 0)
|
|
catSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], 0.3)
|
|
catSep:Hide()
|
|
row.catSep = catSep
|
|
|
|
local highlight = row:CreateTexture(nil, "HIGHLIGHT")
|
|
highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
|
|
highlight:SetBlendMode("ADD")
|
|
highlight:SetAllPoints(row)
|
|
highlight:SetAlpha(0.3)
|
|
row.highlight = highlight
|
|
|
|
local selectedBg = row:CreateTexture(nil, "ARTWORK")
|
|
selectedBg:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
selectedBg:SetAllPoints(row)
|
|
selectedBg:SetVertexColor(T.selectedRowBg[1], T.selectedRowBg[2], T.selectedRowBg[3], 0.40)
|
|
selectedBg:Hide()
|
|
row.selectedBg = selectedBg
|
|
|
|
local selectedGlow = row:CreateTexture(nil, "ARTWORK")
|
|
selectedGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
selectedGlow:SetPoint("TOPLEFT", row, "TOPLEFT", 0, 0)
|
|
selectedGlow:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
|
|
selectedGlow:SetWidth(4)
|
|
selectedGlow:SetVertexColor(1, 0.65, 0.85, 1)
|
|
selectedGlow:Hide()
|
|
row.selectedGlow = selectedGlow
|
|
|
|
local selTop = row:CreateTexture(nil, "OVERLAY")
|
|
selTop:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
selTop:SetHeight(1)
|
|
selTop:SetPoint("TOPLEFT", row, "TOPLEFT", 0, 0)
|
|
selTop:SetPoint("TOPRIGHT", row, "TOPRIGHT", 0, 0)
|
|
selTop:SetVertexColor(T.selectedRowBorder[1], T.selectedRowBorder[2], T.selectedRowBorder[3], T.selectedRowBorder[4])
|
|
selTop:Hide()
|
|
row.selTop = selTop
|
|
|
|
local selBot = row:CreateTexture(nil, "OVERLAY")
|
|
selBot:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
selBot:SetHeight(1)
|
|
selBot:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
|
|
selBot:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", 0, 0)
|
|
selBot:SetVertexColor(T.selectedRowBorder[1], T.selectedRowBorder[2], T.selectedRowBorder[3], T.selectedRowBorder[4])
|
|
selBot:Hide()
|
|
row.selBot = selBot
|
|
|
|
row.serviceIndex = nil
|
|
row.isHeader = false
|
|
|
|
row:SetScript("OnEnter", function()
|
|
if this.serviceIndex and not this.isHeader then
|
|
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
|
local ok = pcall(GameTooltip.SetTrainerService, GameTooltip, this.serviceIndex)
|
|
if ok then
|
|
GameTooltip:Show()
|
|
else
|
|
GameTooltip:Hide()
|
|
end
|
|
end
|
|
end)
|
|
row:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
|
|
function row:SetAsHeader(name, collapsed)
|
|
self.isHeader = true
|
|
self:SetHeight(CAT_ROW_H)
|
|
self.iconFrame:Hide()
|
|
self.qualGlow:Hide()
|
|
self.nameFS:Hide()
|
|
self.subFS:Hide()
|
|
self.costFS:Hide()
|
|
self.costMoney:Hide()
|
|
self.selectedBg:Hide()
|
|
self.selectedGlow:Hide()
|
|
self.selTop:Hide()
|
|
self.selBot:Hide()
|
|
self.highlight:SetAlpha(0.15)
|
|
local arrow = collapsed and "+" or "-"
|
|
self.catFS:SetText(arrow .. " " .. (name or ""))
|
|
self.catFS:Show()
|
|
self.catSep:Show()
|
|
end
|
|
|
|
function row:SetAsService(svc)
|
|
self.isHeader = false
|
|
self.serviceIndex = svc.index
|
|
self:SetHeight(LIST_ROW_H)
|
|
self.iconFrame:Show()
|
|
self.nameFS:Show()
|
|
self.subFS:Show()
|
|
self.costFS:Hide()
|
|
self.costMoney:Show()
|
|
self.catFS:Hide()
|
|
self.catSep:Hide()
|
|
self.highlight:SetAlpha(0.3)
|
|
|
|
local iconTex = GetTrainerServiceIcon and GetTrainerServiceIcon(svc.index)
|
|
self.icon:SetTexture(iconTex)
|
|
self.nameFS:SetText(svc.name)
|
|
self.subFS:SetText(svc.subText)
|
|
|
|
if svc.category == "available" then
|
|
self.nameFS:SetTextColor(T.available[1], T.available[2], T.available[3])
|
|
self.icon:SetVertexColor(1, 1, 1)
|
|
elseif svc.category == "unavailable" then
|
|
self.nameFS:SetTextColor(T.unavailable[1], T.unavailable[2], T.unavailable[3])
|
|
self.icon:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
else
|
|
self.nameFS:SetTextColor(T.used[1], T.used[2], T.used[3])
|
|
self.icon:SetVertexColor(T.passive[1], T.passive[2], T.passive[3])
|
|
end
|
|
|
|
local quality = GetCachedServiceQuality(svc.index)
|
|
local qc = QUALITY_COLORS[quality]
|
|
if qc and quality and quality >= 2 then
|
|
self.qualGlow:SetVertexColor(qc[1], qc[2], qc[3])
|
|
self.qualGlow:Show()
|
|
self.iconFrame:SetBackdropBorderColor(qc[1], qc[2], qc[3], 1)
|
|
else
|
|
self.qualGlow:Hide()
|
|
self.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
|
end
|
|
|
|
local ok, cost = pcall(GetTrainerServiceCost, svc.index)
|
|
if ok and cost and cost > 0 then
|
|
SetMoneyFrame(self.costMoney, cost)
|
|
else
|
|
SetMoneyFrame(self.costMoney, 0)
|
|
end
|
|
end
|
|
|
|
function row:Clear()
|
|
self.serviceIndex = nil
|
|
self.isHeader = false
|
|
self.qualGlow:Hide()
|
|
self.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
|
self.selectedBg:Hide()
|
|
self.selectedGlow:Hide()
|
|
self.selTop:Hide()
|
|
self.selBot:Hide()
|
|
self.costMoney:Hide()
|
|
self:Hide()
|
|
end
|
|
|
|
return row
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Build Display List (with categories)
|
|
--------------------------------------------------------------------------------
|
|
local function BuildDisplayList()
|
|
displayList = {}
|
|
local numServices = GetNumTrainerServices and GetNumTrainerServices() or 0
|
|
if numServices == 0 then return end
|
|
|
|
local currentCat = nil
|
|
local catServices = {}
|
|
local categories = {}
|
|
local catOrder = {}
|
|
|
|
for i = 1, numServices do
|
|
local name, subText, category = GetTrainerServiceInfo(i)
|
|
if name then
|
|
local isHdr = IsServiceHeader(i)
|
|
if isHdr then
|
|
currentCat = name
|
|
if not catServices[name] then
|
|
catServices[name] = {}
|
|
table.insert(catOrder, name)
|
|
end
|
|
else
|
|
if not currentCat then
|
|
currentCat = "技能"
|
|
if not catServices[currentCat] then
|
|
catServices[currentCat] = {}
|
|
table.insert(catOrder, currentCat)
|
|
end
|
|
end
|
|
local show = false
|
|
if currentFilter == "all" then
|
|
show = true
|
|
elseif currentFilter == (category or "") then
|
|
show = true
|
|
end
|
|
if show then
|
|
table.insert(catServices[currentCat], {
|
|
index = i,
|
|
name = name,
|
|
subText = subText or "",
|
|
category = category or "unavailable",
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local hasCats = table.getn(catOrder) > 1
|
|
|
|
for _, catName in ipairs(catOrder) do
|
|
local svcs = catServices[catName]
|
|
if table.getn(svcs) > 0 then
|
|
if hasCats then
|
|
table.insert(displayList, {
|
|
type = "header",
|
|
name = catName,
|
|
collapsed = collapsedCats[catName],
|
|
})
|
|
end
|
|
if not collapsedCats[catName] then
|
|
for _, svc in ipairs(svcs) do
|
|
table.insert(displayList, {
|
|
type = "service",
|
|
data = svc,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if table.getn(catOrder) <= 1 and table.getn(displayList) == 0 then
|
|
local allCat = catOrder[1] or "技能"
|
|
local svcs = catServices[allCat] or {}
|
|
for _, svc in ipairs(svcs) do
|
|
table.insert(displayList, { type = "service", data = svc })
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Update Functions
|
|
--------------------------------------------------------------------------------
|
|
local function UpdateList()
|
|
if not MainFrame or not MainFrame:IsVisible() then return end
|
|
|
|
BuildDisplayList()
|
|
|
|
local content = MainFrame.listScroll.content
|
|
local count = table.getn(displayList)
|
|
local y = 0
|
|
|
|
for i = 1, MAX_ROWS do
|
|
local row = rowButtons[i]
|
|
if i <= count then
|
|
local entry = displayList[i]
|
|
row:ClearAllPoints()
|
|
|
|
if entry.type == "header" then
|
|
row:SetAsHeader(entry.name, entry.collapsed)
|
|
row:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y)
|
|
row.catName = entry.name
|
|
row:Show()
|
|
y = y + CAT_ROW_H
|
|
else
|
|
row:SetAsService(entry.data)
|
|
row:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y)
|
|
row.catName = nil
|
|
row:Show()
|
|
y = y + LIST_ROW_H
|
|
|
|
if selectedIndex == entry.data.index then
|
|
row.iconFrame:SetBackdropBorderColor(1, 0.65, 0.85, 1)
|
|
row.iconFrame:SetBackdropColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 0.5)
|
|
row.selectedBg:Show()
|
|
row.selectedGlow:Show()
|
|
row.selTop:Show()
|
|
row.selBot:Show()
|
|
row.nameFS:SetTextColor(T.selectedNameText[1], T.selectedNameText[2], T.selectedNameText[3])
|
|
else
|
|
row.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
|
row.iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
|
|
row.selectedBg:Hide()
|
|
row.selectedGlow:Hide()
|
|
row.selTop:Hide()
|
|
row.selBot:Hide()
|
|
end
|
|
end
|
|
else
|
|
row:Clear()
|
|
end
|
|
end
|
|
|
|
content:SetHeight(math.max(1, y))
|
|
end
|
|
|
|
local function UpdateDetail()
|
|
if not MainFrame then return end
|
|
local detail = MainFrame.detail
|
|
|
|
if not selectedIndex then
|
|
detail.iconFrame:Hide()
|
|
detail.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
|
detail.nameFS:SetText("")
|
|
detail.subFS:SetText("")
|
|
detail.reqFS:SetText("")
|
|
detail.infoFS:SetText("")
|
|
detail.descFS:SetText("")
|
|
detail.descDivider:Hide()
|
|
MainFrame.trainBtn:SetDisabled(true)
|
|
SetMoneyFrame(MainFrame.costMoney, 0)
|
|
SetMoneyFrame(MainFrame.availMoney, 0)
|
|
MainFrame.costLabel:SetText("花费:")
|
|
return
|
|
end
|
|
|
|
local name, subText, category = GetTrainerServiceInfo(selectedIndex)
|
|
local iconTex = GetTrainerServiceIcon and GetTrainerServiceIcon(selectedIndex)
|
|
local ok, cost = pcall(GetTrainerServiceCost, selectedIndex)
|
|
if not ok then cost = 0 end
|
|
local levelReq = 0
|
|
if GetTrainerServiceLevelReq then
|
|
local ok2, lr = pcall(GetTrainerServiceLevelReq, selectedIndex)
|
|
if ok2 then levelReq = lr or 0 end
|
|
end
|
|
|
|
detail.icon:SetTexture(iconTex)
|
|
detail.iconFrame:Show()
|
|
|
|
local quality = GetServiceQuality(selectedIndex)
|
|
local qc = QUALITY_COLORS[quality]
|
|
if qc and quality and quality >= 2 then
|
|
detail.iconFrame:SetBackdropBorderColor(qc[1], qc[2], qc[3], 1)
|
|
else
|
|
detail.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
|
end
|
|
|
|
detail.nameFS:SetText(name or "")
|
|
|
|
if category == "available" then
|
|
detail.nameFS:SetTextColor(T.available[1], T.available[2], T.available[3])
|
|
elseif category == "unavailable" then
|
|
detail.nameFS:SetTextColor(T.unavailable[1], T.unavailable[2], T.unavailable[3])
|
|
else
|
|
detail.nameFS:SetTextColor(T.used[1], T.used[2], T.used[3])
|
|
end
|
|
|
|
detail.subFS:SetText(subText or "")
|
|
|
|
local reqParts = {}
|
|
if levelReq and levelReq > 0 then
|
|
local pLvl = UnitLevel("player") or 60
|
|
local color = pLvl >= levelReq and "|cff40ff40" or "|cffff4040"
|
|
table.insert(reqParts, color .. "需要等级 " .. levelReq .. "|r")
|
|
end
|
|
if GetTrainerServiceSkillReq then
|
|
local ok3, skillName, skillRank, hasReq = pcall(GetTrainerServiceSkillReq, selectedIndex)
|
|
if ok3 and skillName and skillName ~= "" then
|
|
local color = hasReq and "|cff40ff40" or "|cffff4040"
|
|
table.insert(reqParts, color .. "需要 " .. skillName .. " (" .. (skillRank or 0) .. ")|r")
|
|
end
|
|
end
|
|
detail.reqFS:SetText(table.concat(reqParts, " "))
|
|
|
|
local spellInfo, descText = GetServiceTooltipInfo(selectedIndex)
|
|
detail.infoFS:SetText(spellInfo)
|
|
detail.descFS:SetText(descText)
|
|
detail.descDivider:Show()
|
|
|
|
local textH = detail.descFS:GetHeight() or 40
|
|
detail.descScroll:GetScrollChild():SetHeight(math.max(1, textH))
|
|
detail.descScroll:SetVerticalScroll(0)
|
|
|
|
local canTrain = (category == "available") and cost and (GetMoney() >= cost)
|
|
MainFrame.trainBtn:SetDisabled(not canTrain)
|
|
|
|
if cost and cost > 0 then
|
|
MainFrame.costLabel:SetText("花费:")
|
|
SetMoneyFrame(MainFrame.costMoney, cost)
|
|
else
|
|
MainFrame.costLabel:SetText("花费: 免费")
|
|
SetMoneyFrame(MainFrame.costMoney, 0)
|
|
end
|
|
SetMoneyFrame(MainFrame.availMoney, GetMoney())
|
|
end
|
|
|
|
local function UpdateFilters()
|
|
if not MainFrame then return end
|
|
MainFrame.filterAll:SetActive(currentFilter == "all")
|
|
MainFrame.filterAvail:SetActive(currentFilter == "available")
|
|
MainFrame.filterUnavail:SetActive(currentFilter == "unavailable")
|
|
MainFrame.filterUsed:SetActive(currentFilter == "used")
|
|
end
|
|
|
|
local function FullUpdate()
|
|
UpdateFilters()
|
|
UpdateList()
|
|
UpdateDetail()
|
|
end
|
|
|
|
local function SelectService(index)
|
|
selectedIndex = index
|
|
local ok = pcall(SelectTrainerService, index)
|
|
FullUpdate()
|
|
end
|
|
|
|
local function ToggleCategory(catName)
|
|
if collapsedCats[catName] then
|
|
collapsedCats[catName] = nil
|
|
else
|
|
collapsedCats[catName] = true
|
|
end
|
|
FullUpdate()
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Initialize
|
|
--------------------------------------------------------------------------------
|
|
function TUI:Initialize()
|
|
if MainFrame then return end
|
|
|
|
MainFrame = CreateFrame("Frame", "SFramesTrainerFrame", UIParent)
|
|
MainFrame:SetWidth(FRAME_W)
|
|
MainFrame:SetHeight(FRAME_H)
|
|
MainFrame:SetPoint("LEFT", UIParent, "LEFT", 64, 0)
|
|
MainFrame:SetFrameStrata("HIGH")
|
|
MainFrame:SetToplevel(true)
|
|
MainFrame:EnableMouse(true)
|
|
MainFrame:SetMovable(true)
|
|
MainFrame:RegisterForDrag("LeftButton")
|
|
MainFrame:SetScript("OnDragStart", function() this:StartMoving() end)
|
|
MainFrame:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
|
|
|
SetRoundBackdrop(MainFrame)
|
|
CreateShadow(MainFrame)
|
|
|
|
-- Header
|
|
local header = CreateFrame("Frame", nil, MainFrame)
|
|
header:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", 0, 0)
|
|
header:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", 0, 0)
|
|
header:SetHeight(HEADER_H)
|
|
header:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
|
|
header:SetBackdropColor(T.headerBg[1], T.headerBg[2], T.headerBg[3], T.headerBg[4])
|
|
|
|
local titleIco = SFrames:CreateIcon(header, "spellbook", 16)
|
|
titleIco:SetDrawLayer("OVERLAY")
|
|
titleIco:SetPoint("LEFT", header, "LEFT", SIDE_PAD, 0)
|
|
titleIco:SetVertexColor(T.gold[1], T.gold[2], T.gold[3])
|
|
|
|
local npcNameFS = header:CreateFontString(nil, "OVERLAY")
|
|
npcNameFS:SetFont(GetFont(), 14, "OUTLINE")
|
|
npcNameFS:SetPoint("LEFT", titleIco, "RIGHT", 5, 0)
|
|
npcNameFS:SetPoint("RIGHT", header, "RIGHT", -30, 0)
|
|
npcNameFS:SetJustifyH("LEFT")
|
|
npcNameFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
|
MainFrame.npcNameFS = npcNameFS
|
|
|
|
local closeBtn = CreateFrame("Button", nil, header)
|
|
closeBtn:SetWidth(20); closeBtn:SetHeight(20)
|
|
closeBtn:SetPoint("TOPRIGHT", header, "TOPRIGHT", -8, -6)
|
|
local closeTex = closeBtn:CreateTexture(nil, "ARTWORK")
|
|
closeTex:SetTexture("Interface\\AddOns\\Nanami-UI\\img\\icon")
|
|
closeTex:SetTexCoord(0.25, 0.375, 0, 0.125)
|
|
closeTex:SetAllPoints()
|
|
closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
closeBtn:SetScript("OnClick", function() MainFrame:Hide() end)
|
|
closeBtn:SetScript("OnEnter", function() closeTex:SetVertexColor(1, 0.6, 0.7) end)
|
|
closeBtn:SetScript("OnLeave", function() closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3]) end)
|
|
|
|
local headerSep = MainFrame:CreateTexture(nil, "ARTWORK")
|
|
headerSep:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
headerSep:SetHeight(1)
|
|
headerSep:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", 6, -HEADER_H)
|
|
headerSep:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", -6, -HEADER_H)
|
|
headerSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
|
|
|
-- Filter bar
|
|
local filterBar = CreateFrame("Frame", nil, MainFrame)
|
|
filterBar:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", SIDE_PAD, -(HEADER_H + 4))
|
|
filterBar:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", -SIDE_PAD, -(HEADER_H + 4))
|
|
filterBar:SetHeight(FILTER_H)
|
|
|
|
local fAll = CreateFilterBtn(filterBar, "全部", 52)
|
|
fAll:SetPoint("LEFT", filterBar, "LEFT", 0, 0)
|
|
fAll:SetScript("OnClick", function() currentFilter = "all"; FullUpdate() end)
|
|
MainFrame.filterAll = fAll
|
|
|
|
local fAvail = CreateFilterBtn(filterBar, "可学习", 60)
|
|
fAvail:SetPoint("LEFT", fAll, "RIGHT", 4, 0)
|
|
fAvail:SetScript("OnClick", function() currentFilter = "available"; FullUpdate() end)
|
|
MainFrame.filterAvail = fAvail
|
|
|
|
local fUnavail = CreateFilterBtn(filterBar, "不可学", 60)
|
|
fUnavail:SetPoint("LEFT", fAvail, "RIGHT", 4, 0)
|
|
fUnavail:SetScript("OnClick", function() currentFilter = "unavailable"; FullUpdate() end)
|
|
MainFrame.filterUnavail = fUnavail
|
|
|
|
local fUsed = CreateFilterBtn(filterBar, "已学会", 60)
|
|
fUsed:SetPoint("LEFT", fUnavail, "RIGHT", 4, 0)
|
|
fUsed:SetScript("OnClick", function() currentFilter = "used"; FullUpdate() end)
|
|
MainFrame.filterUsed = fUsed
|
|
|
|
-- Scrollable list area
|
|
local listTop = HEADER_H + FILTER_H + 8
|
|
local listBottom = DETAIL_H + BOTTOM_H + 8
|
|
|
|
local listScroll = CreateFrame("ScrollFrame", "SFramesTrainerListScroll", MainFrame)
|
|
listScroll:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", SIDE_PAD, -listTop)
|
|
listScroll:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, listBottom)
|
|
|
|
local listContent = CreateFrame("Frame", "SFramesTrainerListContent", listScroll)
|
|
listContent:SetWidth(CONTENT_W)
|
|
listContent:SetHeight(1)
|
|
listScroll:SetScrollChild(listContent)
|
|
|
|
listScroll:EnableMouseWheel(true)
|
|
listScroll:SetScript("OnMouseWheel", function()
|
|
local cur = this:GetVerticalScroll()
|
|
local maxVal = this:GetVerticalScrollRange()
|
|
if arg1 > 0 then
|
|
this:SetVerticalScroll(math.max(0, cur - SCROLL_STEP))
|
|
else
|
|
this:SetVerticalScroll(math.min(maxVal, cur + SCROLL_STEP))
|
|
end
|
|
end)
|
|
listScroll.content = listContent
|
|
MainFrame.listScroll = listScroll
|
|
|
|
for i = 1, MAX_ROWS do
|
|
local row = CreateListRow(listContent, i)
|
|
row:SetScript("OnClick", function()
|
|
if this.isHeader and this.catName then
|
|
ToggleCategory(this.catName)
|
|
elseif this.serviceIndex then
|
|
SelectService(this.serviceIndex)
|
|
end
|
|
end)
|
|
rowButtons[i] = row
|
|
end
|
|
|
|
-- Detail separator
|
|
local detailSep = MainFrame:CreateTexture(nil, "ARTWORK")
|
|
detailSep:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
detailSep:SetHeight(1)
|
|
detailSep:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", 6, DETAIL_H + BOTTOM_H + 4)
|
|
detailSep:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -6, DETAIL_H + BOTTOM_H + 4)
|
|
detailSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
|
|
|
-- Detail area
|
|
local detail = CreateFrame("Frame", nil, MainFrame)
|
|
detail:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", SIDE_PAD, BOTTOM_H + 4)
|
|
detail:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, BOTTOM_H + 4)
|
|
detail:SetHeight(DETAIL_H)
|
|
MainFrame.detail = detail
|
|
|
|
local dIconFrame = CreateFrame("Frame", nil, detail)
|
|
dIconFrame:SetWidth(42); dIconFrame:SetHeight(42)
|
|
dIconFrame:SetPoint("TOPLEFT", detail, "TOPLEFT", 2, -6)
|
|
dIconFrame:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 14,
|
|
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
|
})
|
|
dIconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
|
|
dIconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
|
dIconFrame:Hide()
|
|
detail.iconFrame = dIconFrame
|
|
|
|
local dIcon = dIconFrame:CreateTexture(nil, "ARTWORK")
|
|
dIcon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
|
dIcon:SetPoint("TOPLEFT", dIconFrame, "TOPLEFT", 3, -3)
|
|
dIcon:SetPoint("BOTTOMRIGHT", dIconFrame, "BOTTOMRIGHT", -3, 3)
|
|
detail.icon = dIcon
|
|
|
|
local dNameFS = detail:CreateFontString(nil, "OVERLAY")
|
|
dNameFS:SetFont(GetFont(), 13, "OUTLINE")
|
|
dNameFS:SetPoint("TOPLEFT", dIconFrame, "TOPRIGHT", 8, -2)
|
|
dNameFS:SetPoint("RIGHT", detail, "RIGHT", -4, 0)
|
|
dNameFS:SetJustifyH("LEFT")
|
|
dNameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
|
|
detail.nameFS = dNameFS
|
|
|
|
local dSubFS = detail:CreateFontString(nil, "OVERLAY")
|
|
dSubFS:SetFont(GetFont(), 11, "OUTLINE")
|
|
dSubFS:SetPoint("TOPLEFT", dNameFS, "BOTTOMLEFT", 0, -2)
|
|
dSubFS:SetJustifyH("LEFT")
|
|
dSubFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
detail.subFS = dSubFS
|
|
|
|
local dReqFS = detail:CreateFontString(nil, "OVERLAY")
|
|
dReqFS:SetFont(GetFont(), 11, "OUTLINE")
|
|
dReqFS:SetPoint("TOPLEFT", dIconFrame, "BOTTOMLEFT", 0, -6)
|
|
dReqFS:SetPoint("RIGHT", detail, "RIGHT", -4, 0)
|
|
dReqFS:SetJustifyH("LEFT")
|
|
detail.reqFS = dReqFS
|
|
|
|
local dInfoFS = detail:CreateFontString(nil, "OVERLAY")
|
|
dInfoFS:SetFont(GetFont(), 11)
|
|
dInfoFS:SetPoint("TOPLEFT", dReqFS, "BOTTOMLEFT", 0, -4)
|
|
dInfoFS:SetPoint("RIGHT", detail, "RIGHT", -4, 0)
|
|
dInfoFS:SetJustifyH("LEFT")
|
|
dInfoFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
|
|
detail.infoFS = dInfoFS
|
|
|
|
local descDivider = detail:CreateTexture(nil, "ARTWORK")
|
|
descDivider:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
descDivider:SetHeight(1)
|
|
descDivider:SetPoint("TOPLEFT", dInfoFS, "BOTTOMLEFT", 0, -5)
|
|
descDivider:SetPoint("RIGHT", detail, "RIGHT", -4, 0)
|
|
descDivider:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], 0.3)
|
|
detail.descDivider = descDivider
|
|
|
|
local descScroll = CreateFrame("ScrollFrame", nil, detail)
|
|
descScroll:SetPoint("TOPLEFT", descDivider, "BOTTOMLEFT", 0, -4)
|
|
descScroll:SetPoint("BOTTOMRIGHT", detail, "BOTTOMRIGHT", -4, 2)
|
|
descScroll:EnableMouseWheel(true)
|
|
descScroll:SetScript("OnMouseWheel", function()
|
|
local cur = this:GetVerticalScroll()
|
|
local maxVal = this:GetVerticalScrollRange()
|
|
if arg1 > 0 then
|
|
this:SetVerticalScroll(math.max(0, cur - 14))
|
|
else
|
|
this:SetVerticalScroll(math.min(maxVal, cur + 14))
|
|
end
|
|
end)
|
|
detail.descScroll = descScroll
|
|
|
|
local descContent = CreateFrame("Frame", nil, descScroll)
|
|
descContent:SetWidth(CONTENT_W - 8)
|
|
descContent:SetHeight(1)
|
|
descScroll:SetScrollChild(descContent)
|
|
|
|
local dDescFS = descContent:CreateFontString(nil, "OVERLAY")
|
|
dDescFS:SetFont(GetFont(), 11)
|
|
dDescFS:SetPoint("TOPLEFT", descContent, "TOPLEFT", 0, 0)
|
|
dDescFS:SetWidth(CONTENT_W - 8)
|
|
dDescFS:SetJustifyH("LEFT")
|
|
dDescFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
|
|
detail.descFS = dDescFS
|
|
|
|
-- Bottom bar
|
|
local bottomSep = MainFrame:CreateTexture(nil, "ARTWORK")
|
|
bottomSep:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
bottomSep:SetHeight(1)
|
|
bottomSep:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", 6, BOTTOM_H)
|
|
bottomSep:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -6, BOTTOM_H)
|
|
bottomSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
|
|
|
local costLabel = MainFrame:CreateFontString(nil, "OVERLAY")
|
|
costLabel:SetFont(GetFont(), 11, "OUTLINE")
|
|
costLabel:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", SIDE_PAD, 32)
|
|
costLabel:SetJustifyH("LEFT")
|
|
costLabel:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
costLabel:SetText("花费:")
|
|
MainFrame.costLabel = costLabel
|
|
|
|
local costMoney = CreateFrame("Frame", "SFramesTrainerCostMoney", MainFrame, "SmallMoneyFrameTemplate")
|
|
costMoney:SetPoint("LEFT", costLabel, "RIGHT", 4, 0)
|
|
costMoney:SetWidth(120)
|
|
costMoney:SetHeight(14)
|
|
costMoney:SetFrameLevel(MainFrame:GetFrameLevel() + 5)
|
|
costMoney:UnregisterAllEvents()
|
|
costMoney:SetScript("OnEvent", nil)
|
|
costMoney:SetScript("OnShow", nil)
|
|
costMoney.moneyType = nil
|
|
costMoney.small = 1
|
|
MainFrame.costMoney = costMoney
|
|
|
|
local availLabel = MainFrame:CreateFontString(nil, "OVERLAY")
|
|
availLabel:SetFont(GetFont(), 11, "OUTLINE")
|
|
availLabel:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", SIDE_PAD, 16)
|
|
availLabel:SetJustifyH("LEFT")
|
|
availLabel:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
availLabel:SetText("可用:")
|
|
MainFrame.availLabel = availLabel
|
|
|
|
local availMoney = CreateFrame("Frame", "SFramesTrainerAvailMoney", MainFrame, "SmallMoneyFrameTemplate")
|
|
availMoney:SetPoint("LEFT", availLabel, "RIGHT", 4, 0)
|
|
availMoney:SetWidth(120)
|
|
availMoney:SetHeight(14)
|
|
availMoney:SetFrameLevel(MainFrame:GetFrameLevel() + 5)
|
|
availMoney:UnregisterAllEvents()
|
|
availMoney:SetScript("OnEvent", nil)
|
|
availMoney:SetScript("OnShow", nil)
|
|
availMoney.moneyType = nil
|
|
availMoney.small = 1
|
|
MainFrame.availMoney = availMoney
|
|
|
|
local trainBtn = CreateActionBtn(MainFrame, "训练", 80)
|
|
trainBtn:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -(SIDE_PAD + 90), 8)
|
|
trainBtn:SetScript("OnClick", function()
|
|
if this.disabled then return end
|
|
if IsControlKeyDown() and BuyTrainerService then
|
|
local numServices = GetNumTrainerServices and GetNumTrainerServices() or 0
|
|
local gold = GetMoney()
|
|
for i = 1, numServices do
|
|
local name, _, category = GetTrainerServiceInfo(i)
|
|
if name and category == "available" and not IsServiceHeader(i) then
|
|
local ok, cost = pcall(GetTrainerServiceCost, i)
|
|
if ok and cost and gold >= cost then
|
|
pcall(BuyTrainerService, i)
|
|
gold = gold - cost
|
|
end
|
|
end
|
|
end
|
|
return
|
|
end
|
|
if selectedIndex and BuyTrainerService then
|
|
BuyTrainerService(selectedIndex)
|
|
end
|
|
end)
|
|
trainBtn:SetScript("OnEnter", function()
|
|
if not this.disabled then
|
|
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
|
|
this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
|
|
this.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3])
|
|
end
|
|
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
|
GameTooltip:AddLine("Ctrl+点击: 学习所有可学技能", 0.8, 0.8, 0.8)
|
|
GameTooltip:Show()
|
|
end)
|
|
trainBtn:SetScript("OnLeave", function()
|
|
if not this.disabled then
|
|
this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
|
|
this.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
end
|
|
GameTooltip:Hide()
|
|
end)
|
|
MainFrame.trainBtn = trainBtn
|
|
|
|
local exitBtn = CreateActionBtn(MainFrame, "退出", 80)
|
|
exitBtn:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, 8)
|
|
exitBtn:SetScript("OnClick", function() MainFrame:Hide() end)
|
|
|
|
-- Events
|
|
local function CleanupBlizzardTrainer()
|
|
if not ClassTrainerFrame then return end
|
|
ClassTrainerFrame:SetScript("OnHide", function() end)
|
|
if HideUIPanel then pcall(HideUIPanel, ClassTrainerFrame) end
|
|
if ClassTrainerFrame:IsVisible() then ClassTrainerFrame:Hide() end
|
|
ClassTrainerFrame:SetAlpha(0)
|
|
ClassTrainerFrame:EnableMouse(false)
|
|
end
|
|
|
|
MainFrame:SetScript("OnHide", function()
|
|
if CloseTrainer then pcall(CloseTrainer) end
|
|
if CloseGossip then pcall(CloseGossip) end
|
|
CleanupBlizzardTrainer()
|
|
end)
|
|
|
|
MainFrame:RegisterEvent("TRAINER_SHOW")
|
|
MainFrame:RegisterEvent("TRAINER_UPDATE")
|
|
MainFrame:RegisterEvent("TRAINER_CLOSED")
|
|
MainFrame:SetScript("OnEvent", function()
|
|
if event == "TRAINER_SHOW" then
|
|
if ClassTrainerFrame then
|
|
ClassTrainerFrame:SetScript("OnHide", function() end)
|
|
ClassTrainerFrame:SetAlpha(0)
|
|
ClassTrainerFrame:EnableMouse(false)
|
|
end
|
|
|
|
if SetTrainerServiceTypeFilter then
|
|
SetTrainerServiceTypeFilter("available", 1)
|
|
SetTrainerServiceTypeFilter("unavailable", 1)
|
|
SetTrainerServiceTypeFilter("used", 1)
|
|
end
|
|
|
|
selectedIndex = nil
|
|
currentFilter = "all"
|
|
collapsedCats = {}
|
|
qualityCache = {}
|
|
local npcName = UnitName("npc") or "训练师"
|
|
if IsTradeskillTrainer and IsTradeskillTrainer() then
|
|
npcName = npcName .. " - 专业训练"
|
|
end
|
|
MainFrame.npcNameFS:SetText(npcName)
|
|
MainFrame:Show()
|
|
BuildDisplayList()
|
|
for _, entry in ipairs(displayList) do
|
|
if entry.type == "service" then
|
|
selectedIndex = entry.data.index
|
|
pcall(SelectTrainerService, entry.data.index)
|
|
break
|
|
end
|
|
end
|
|
FullUpdate()
|
|
|
|
MainFrame._hideBlizzTimer = 0
|
|
MainFrame:SetScript("OnUpdate", function()
|
|
if not this._hideBlizzTimer then return end
|
|
this._hideBlizzTimer = this._hideBlizzTimer + arg1
|
|
if this._hideBlizzTimer > 0.05 then
|
|
this._hideBlizzTimer = nil
|
|
this:SetScript("OnUpdate", nil)
|
|
CleanupBlizzardTrainer()
|
|
end
|
|
end)
|
|
elseif event == "TRAINER_UPDATE" then
|
|
if MainFrame:IsVisible() then FullUpdate() end
|
|
elseif event == "TRAINER_CLOSED" then
|
|
MainFrame._hideBlizzTimer = nil
|
|
MainFrame:SetScript("OnUpdate", nil)
|
|
CleanupBlizzardTrainer()
|
|
MainFrame:Hide()
|
|
end
|
|
end)
|
|
MainFrame:Hide()
|
|
|
|
tinsert(UISpecialFrames, "SFramesTrainerFrame")
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Bootstrap
|
|
--------------------------------------------------------------------------------
|
|
local bootstrap = CreateFrame("Frame")
|
|
bootstrap:RegisterEvent("PLAYER_LOGIN")
|
|
bootstrap:RegisterEvent("ADDON_LOADED")
|
|
bootstrap:SetScript("OnEvent", function()
|
|
if event == "PLAYER_LOGIN" then
|
|
if SFramesDB.enableTrainer == nil then
|
|
SFramesDB.enableTrainer = true
|
|
end
|
|
if SFramesDB.enableTrainer ~= false then
|
|
TUI:Initialize()
|
|
end
|
|
elseif event == "ADDON_LOADED" and arg1 == "Blizzard_TrainerUI" then
|
|
if MainFrame and ClassTrainerFrame then
|
|
ClassTrainerFrame:SetScript("OnHide", function() end)
|
|
ClassTrainerFrame:SetAlpha(0)
|
|
ClassTrainerFrame:EnableMouse(false)
|
|
end
|
|
end
|
|
end)
|