完成焦点等开发
This commit is contained in:
428
TrainerUI.lua
428
TrainerUI.lua
@@ -37,18 +37,18 @@ local function ColorToQuality(r, g, b)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Layout
|
||||
-- Layout (Left-Right structure like TradeSkill frame)
|
||||
--------------------------------------------------------------------------------
|
||||
local FRAME_W = 380
|
||||
local FRAME_H = 540
|
||||
local FRAME_W = 620
|
||||
local FRAME_H = 480
|
||||
local HEADER_H = 34
|
||||
local SIDE_PAD = 14
|
||||
local CONTENT_W = FRAME_W - SIDE_PAD * 2
|
||||
local SIDE_PAD = 10
|
||||
local FILTER_H = 28
|
||||
local LIST_ROW_H = 32
|
||||
local LIST_W = 320
|
||||
local DETAIL_W = FRAME_W - LIST_W - SIDE_PAD * 3
|
||||
local LIST_ROW_H = 28
|
||||
local CAT_ROW_H = 20
|
||||
local DETAIL_H = 160
|
||||
local BOTTOM_H = 52
|
||||
local BOTTOM_H = 48
|
||||
local SCROLL_STEP = 40
|
||||
local MAX_ROWS = 60
|
||||
|
||||
@@ -61,6 +61,7 @@ local currentFilter = "all"
|
||||
local displayList = {}
|
||||
local rowButtons = {}
|
||||
local collapsedCats = {}
|
||||
local isTradeskillTrainerCached = false -- Cache to avoid repeated API calls
|
||||
local function HideBlizzardTrainer()
|
||||
if not ClassTrainerFrame then return end
|
||||
ClassTrainerFrame:SetScript("OnHide", function() end)
|
||||
@@ -152,6 +153,58 @@ local function IsServiceHeader(index)
|
||||
return false
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Enhanced category detection for better filtering
|
||||
-- Trust Blizzard API for class trainers (considers spell rank requirements)
|
||||
-- Only do extra verification for tradeskill trainers
|
||||
--------------------------------------------------------------------------------
|
||||
local function GetVerifiedCategory(index)
|
||||
local name, _, category = GetTrainerServiceInfo(index)
|
||||
if not name then return nil end
|
||||
|
||||
-- "used" is always reliable - player already knows this skill
|
||||
if category == "used" then
|
||||
return "used"
|
||||
end
|
||||
|
||||
-- "unavailable" from API should be trusted - it considers:
|
||||
-- - Level requirements
|
||||
-- - Skill rank prerequisites (e.g., need Fireball Rank 1 before Rank 2)
|
||||
-- - Profession skill requirements
|
||||
if category == "unavailable" then
|
||||
return "unavailable"
|
||||
end
|
||||
|
||||
-- For "available", do extra verification only for tradeskill trainers
|
||||
-- Class trainers' "available" is already accurate
|
||||
if category == "available" then
|
||||
-- Additional check for tradeskill trainers (use cached value)
|
||||
if isTradeskillTrainerCached then
|
||||
local playerLevel = UnitLevel("player") or 1
|
||||
|
||||
-- Check level requirement
|
||||
if GetTrainerServiceLevelReq then
|
||||
local ok, levelReq = pcall(GetTrainerServiceLevelReq, index)
|
||||
if ok and levelReq and levelReq > 0 and playerLevel < levelReq then
|
||||
return "unavailable"
|
||||
end
|
||||
end
|
||||
|
||||
-- Check skill requirement
|
||||
if GetTrainerServiceSkillReq then
|
||||
local ok, skillName, skillRank, hasReq = pcall(GetTrainerServiceSkillReq, index)
|
||||
if ok and skillName and skillName ~= "" and not hasReq then
|
||||
return "unavailable"
|
||||
end
|
||||
end
|
||||
end
|
||||
return "available"
|
||||
end
|
||||
|
||||
-- Fallback: unknown category, treat as unavailable
|
||||
return category or "unavailable"
|
||||
end
|
||||
|
||||
local scanTip = nil
|
||||
|
||||
local function GetServiceTooltipInfo(index)
|
||||
@@ -350,7 +403,7 @@ end
|
||||
--------------------------------------------------------------------------------
|
||||
local function CreateListRow(parent, idx)
|
||||
local row = CreateFrame("Button", nil, parent)
|
||||
row:SetWidth(CONTENT_W)
|
||||
row:SetWidth(LIST_W - 30) -- Account for scrollbar
|
||||
row:SetHeight(LIST_ROW_H)
|
||||
|
||||
local iconFrame = CreateFrame("Frame", nil, row)
|
||||
@@ -365,6 +418,7 @@ local function CreateListRow(parent, idx)
|
||||
})
|
||||
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])
|
||||
iconFrame:EnableMouse(false) -- Don't block mouse events
|
||||
row.iconFrame = iconFrame
|
||||
|
||||
local icon = iconFrame:CreateTexture(nil, "ARTWORK")
|
||||
@@ -384,16 +438,17 @@ local function CreateListRow(parent, idx)
|
||||
row.qualGlow = qualGlow
|
||||
|
||||
local nameFS = row:CreateFontString(nil, "OVERLAY")
|
||||
nameFS:SetFont(GetFont(), 12, "OUTLINE")
|
||||
nameFS:SetPoint("LEFT", iconFrame, "RIGHT", 6, 2)
|
||||
nameFS:SetFont(GetFont(), 11, "OUTLINE")
|
||||
nameFS:SetPoint("LEFT", iconFrame, "RIGHT", 6, 5)
|
||||
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:SetFont(GetFont(), 9, "OUTLINE")
|
||||
subFS:SetPoint("LEFT", iconFrame, "RIGHT", 6, -6)
|
||||
subFS:SetPoint("RIGHT", row, "RIGHT", -90, 0)
|
||||
subFS:SetJustifyH("LEFT")
|
||||
subFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
||||
row.subFS = subFS
|
||||
@@ -414,6 +469,13 @@ local function CreateListRow(parent, idx)
|
||||
costMoney:UnregisterAllEvents()
|
||||
costMoney:SetScript("OnEvent", nil)
|
||||
costMoney:SetScript("OnShow", nil)
|
||||
costMoney:EnableMouse(false) -- Don't block mouse events
|
||||
-- Disable mouse on child buttons too
|
||||
local moneyName = "SFTRow" .. idx .. "Money"
|
||||
for _, suffix in ipairs({"GoldButton", "SilverButton", "CopperButton"}) do
|
||||
local child = _G[moneyName .. suffix]
|
||||
if child and child.EnableMouse then child:EnableMouse(false) end
|
||||
end
|
||||
costMoney.moneyType = nil
|
||||
costMoney.hasPickup = nil
|
||||
costMoney.small = 1
|
||||
@@ -542,12 +604,18 @@ local function CreateListRow(parent, idx)
|
||||
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)
|
||||
-- Skip quality scan for tradeskill trainers (performance optimization)
|
||||
if not isTradeskillTrainerCached then
|
||||
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
|
||||
else
|
||||
self.qualGlow:Hide()
|
||||
self.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
||||
@@ -574,6 +642,21 @@ local function CreateListRow(parent, idx)
|
||||
self:Hide()
|
||||
end
|
||||
|
||||
-- Enable mouse wheel scrolling on rows to pass through to scrollbar
|
||||
row:EnableMouseWheel(true)
|
||||
row:SetScript("OnMouseWheel", function()
|
||||
local scrollBar = MainFrame and MainFrame.scrollBar
|
||||
if scrollBar then
|
||||
local cur = scrollBar:GetValue()
|
||||
local min, max = scrollBar:GetMinMaxValues()
|
||||
if arg1 > 0 then
|
||||
scrollBar:SetValue(math.max(min, cur - SCROLL_STEP))
|
||||
else
|
||||
scrollBar:SetValue(math.min(max, cur + SCROLL_STEP))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
return row
|
||||
end
|
||||
|
||||
@@ -586,8 +669,8 @@ local function BuildDisplayList()
|
||||
if numServices == 0 then return end
|
||||
|
||||
local currentCat = nil
|
||||
local catServices = {}
|
||||
local categories = {}
|
||||
-- Each category stores three buckets: available, used, unavailable
|
||||
local catBuckets = {}
|
||||
local catOrder = {}
|
||||
|
||||
for i = 1, numServices do
|
||||
@@ -596,30 +679,28 @@ local function BuildDisplayList()
|
||||
local isHdr = IsServiceHeader(i)
|
||||
if isHdr then
|
||||
currentCat = name
|
||||
if not catServices[name] then
|
||||
catServices[name] = {}
|
||||
if not catBuckets[name] then
|
||||
catBuckets[name] = { available = {}, used = {}, unavailable = {} }
|
||||
table.insert(catOrder, name)
|
||||
end
|
||||
else
|
||||
if not currentCat then
|
||||
currentCat = "技能"
|
||||
if not catServices[currentCat] then
|
||||
catServices[currentCat] = {}
|
||||
if not catBuckets[currentCat] then
|
||||
catBuckets[currentCat] = { available = {}, used = {}, unavailable = {} }
|
||||
table.insert(catOrder, currentCat)
|
||||
end
|
||||
end
|
||||
local show = false
|
||||
if currentFilter == "all" then
|
||||
show = true
|
||||
elseif currentFilter == (category or "") then
|
||||
show = true
|
||||
end
|
||||
-- Use verified category for more accurate filtering
|
||||
local cat = GetVerifiedCategory(i) or "unavailable"
|
||||
local show = (currentFilter == "all") or (currentFilter == cat)
|
||||
if show then
|
||||
table.insert(catServices[currentCat], {
|
||||
local bucket = catBuckets[currentCat][cat] or catBuckets[currentCat]["unavailable"]
|
||||
table.insert(bucket, {
|
||||
index = i,
|
||||
name = name,
|
||||
subText = subText or "",
|
||||
category = category or "unavailable",
|
||||
category = cat,
|
||||
})
|
||||
end
|
||||
end
|
||||
@@ -629,8 +710,9 @@ local function BuildDisplayList()
|
||||
local hasCats = table.getn(catOrder) > 1
|
||||
|
||||
for _, catName in ipairs(catOrder) do
|
||||
local svcs = catServices[catName]
|
||||
if table.getn(svcs) > 0 then
|
||||
local buckets = catBuckets[catName]
|
||||
local totalCount = table.getn(buckets.available) + table.getn(buckets.used) + table.getn(buckets.unavailable)
|
||||
if totalCount > 0 then
|
||||
if hasCats then
|
||||
table.insert(displayList, {
|
||||
type = "header",
|
||||
@@ -639,23 +721,19 @@ local function BuildDisplayList()
|
||||
})
|
||||
end
|
||||
if not collapsedCats[catName] then
|
||||
for _, svc in ipairs(svcs) do
|
||||
table.insert(displayList, {
|
||||
type = "service",
|
||||
data = svc,
|
||||
})
|
||||
-- available → used → unavailable
|
||||
for _, svc in ipairs(buckets.available) do
|
||||
table.insert(displayList, { type = "service", data = svc })
|
||||
end
|
||||
for _, svc in ipairs(buckets.used) do
|
||||
table.insert(displayList, { type = "service", data = svc })
|
||||
end
|
||||
for _, svc in ipairs(buckets.unavailable) 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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -712,6 +790,18 @@ local function UpdateList()
|
||||
end
|
||||
|
||||
content:SetHeight(math.max(1, y))
|
||||
|
||||
-- Update scrollbar range
|
||||
if MainFrame.scrollBar then
|
||||
local visibleHeight = MainFrame.listScroll:GetHeight() or 1
|
||||
local maxScroll = math.max(0, y - visibleHeight)
|
||||
MainFrame.scrollBar:SetMinMaxValues(0, maxScroll)
|
||||
if maxScroll <= 0 then
|
||||
MainFrame.scrollBar:Hide()
|
||||
else
|
||||
MainFrame.scrollBar:Show()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function UpdateDetail()
|
||||
@@ -734,7 +824,8 @@ local function UpdateDetail()
|
||||
return
|
||||
end
|
||||
|
||||
local name, subText, category = GetTrainerServiceInfo(selectedIndex)
|
||||
local name, subText, _ = GetTrainerServiceInfo(selectedIndex)
|
||||
local category = GetVerifiedCategory(selectedIndex)
|
||||
local iconTex = GetTrainerServiceIcon and GetTrainerServiceIcon(selectedIndex)
|
||||
local ok, cost = pcall(GetTrainerServiceCost, selectedIndex)
|
||||
if not ok then cost = 0 end
|
||||
@@ -904,57 +995,133 @@ function TUI:Initialize()
|
||||
headerSep:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", -6, -HEADER_H)
|
||||
headerSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
||||
|
||||
-- Filter bar
|
||||
-- Filter bar (above left list)
|
||||
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:SetWidth(LIST_W)
|
||||
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)
|
||||
local fAvail = CreateFilterBtn(filterBar, "可学习", 55)
|
||||
fAvail:SetPoint("LEFT", filterBar, "LEFT", 0, 0)
|
||||
fAvail:SetScript("OnClick", function() currentFilter = "available"; FullUpdate() end)
|
||||
MainFrame.filterAvail = fAvail
|
||||
|
||||
local fUnavail = CreateFilterBtn(filterBar, "不可学", 60)
|
||||
fUnavail:SetPoint("LEFT", fAvail, "RIGHT", 4, 0)
|
||||
local fUnavail = CreateFilterBtn(filterBar, "不可学", 55)
|
||||
fUnavail:SetPoint("LEFT", fAvail, "RIGHT", 2, 0)
|
||||
fUnavail:SetScript("OnClick", function() currentFilter = "unavailable"; FullUpdate() end)
|
||||
MainFrame.filterUnavail = fUnavail
|
||||
|
||||
local fUsed = CreateFilterBtn(filterBar, "已学会", 60)
|
||||
fUsed:SetPoint("LEFT", fUnavail, "RIGHT", 4, 0)
|
||||
local fUsed = CreateFilterBtn(filterBar, "已学会", 55)
|
||||
fUsed:SetPoint("LEFT", fUnavail, "RIGHT", 2, 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 fAll = CreateFilterBtn(filterBar, "全部", 45)
|
||||
fAll:SetPoint("LEFT", fUsed, "RIGHT", 2, 0)
|
||||
fAll:SetScript("OnClick", function() currentFilter = "all"; FullUpdate() end)
|
||||
MainFrame.filterAll = fAll
|
||||
|
||||
local listScroll = CreateFrame("ScrollFrame", "SFramesTrainerListScroll", MainFrame)
|
||||
listScroll:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", SIDE_PAD, -listTop)
|
||||
listScroll:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, listBottom)
|
||||
-- Left side: Scrollable list area
|
||||
local listTop = HEADER_H + FILTER_H + 8
|
||||
|
||||
local listBg = CreateFrame("Frame", nil, MainFrame)
|
||||
listBg:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", SIDE_PAD, -listTop)
|
||||
listBg:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", SIDE_PAD, BOTTOM_H + 4)
|
||||
listBg:SetWidth(LIST_W)
|
||||
listBg: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 },
|
||||
})
|
||||
listBg:SetBackdropColor(0, 0, 0, 0.3)
|
||||
listBg:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], 0.5)
|
||||
|
||||
-- Scrollbar
|
||||
local scrollBar = CreateFrame("Slider", "SFramesTrainerScrollBar", listBg)
|
||||
scrollBar:SetWidth(16)
|
||||
scrollBar:SetPoint("TOPRIGHT", listBg, "TOPRIGHT", -4, -4)
|
||||
scrollBar:SetPoint("BOTTOMRIGHT", listBg, "BOTTOMRIGHT", -4, 4)
|
||||
scrollBar:SetOrientation("VERTICAL")
|
||||
scrollBar:SetThumbTexture("Interface\\Buttons\\UI-ScrollBar-Knob")
|
||||
scrollBar:SetMinMaxValues(0, 1)
|
||||
scrollBar:SetValue(0)
|
||||
scrollBar:SetValueStep(SCROLL_STEP)
|
||||
scrollBar:EnableMouseWheel(true)
|
||||
|
||||
local scrollBg = scrollBar:CreateTexture(nil, "BACKGROUND")
|
||||
scrollBg:SetAllPoints()
|
||||
scrollBg:SetTexture(0, 0, 0, 0.3)
|
||||
|
||||
local scrollUp = CreateFrame("Button", nil, scrollBar)
|
||||
scrollUp:SetWidth(16); scrollUp:SetHeight(16)
|
||||
scrollUp:SetPoint("BOTTOM", scrollBar, "TOP", 0, 0)
|
||||
scrollUp:SetNormalTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Up")
|
||||
scrollUp:SetPushedTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Down")
|
||||
scrollUp:SetDisabledTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Disabled")
|
||||
scrollUp:SetHighlightTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Highlight")
|
||||
scrollUp:SetScript("OnClick", function()
|
||||
local val = scrollBar:GetValue()
|
||||
scrollBar:SetValue(val - SCROLL_STEP)
|
||||
end)
|
||||
|
||||
local scrollDown = CreateFrame("Button", nil, scrollBar)
|
||||
scrollDown:SetWidth(16); scrollDown:SetHeight(16)
|
||||
scrollDown:SetPoint("TOP", scrollBar, "BOTTOM", 0, 0)
|
||||
scrollDown:SetNormalTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Up")
|
||||
scrollDown:SetPushedTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Down")
|
||||
scrollDown:SetDisabledTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Disabled")
|
||||
scrollDown:SetHighlightTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Highlight")
|
||||
scrollDown:SetScript("OnClick", function()
|
||||
local val = scrollBar:GetValue()
|
||||
scrollBar:SetValue(val + SCROLL_STEP)
|
||||
end)
|
||||
|
||||
local listScroll = CreateFrame("ScrollFrame", "SFramesTrainerListScroll", listBg)
|
||||
listScroll:SetPoint("TOPLEFT", listBg, "TOPLEFT", 4, -4)
|
||||
listScroll:SetPoint("BOTTOMRIGHT", scrollBar, "BOTTOMLEFT", -2, 0)
|
||||
|
||||
local listContent = CreateFrame("Frame", "SFramesTrainerListContent", listScroll)
|
||||
listContent:SetWidth(CONTENT_W)
|
||||
listContent:SetWidth(LIST_W - 30)
|
||||
listContent:SetHeight(1)
|
||||
listScroll:SetScrollChild(listContent)
|
||||
|
||||
listScroll:EnableMouseWheel(true)
|
||||
listScroll:SetScript("OnMouseWheel", function()
|
||||
local cur = this:GetVerticalScroll()
|
||||
local maxVal = this:GetVerticalScrollRange()
|
||||
-- Sync scrollbar with scroll frame
|
||||
scrollBar:SetScript("OnValueChanged", function()
|
||||
listScroll:SetVerticalScroll(this:GetValue())
|
||||
end)
|
||||
|
||||
scrollBar:SetScript("OnMouseWheel", function()
|
||||
local cur = this:GetValue()
|
||||
local min, max = this:GetMinMaxValues()
|
||||
if arg1 > 0 then
|
||||
this:SetVerticalScroll(math.max(0, cur - SCROLL_STEP))
|
||||
this:SetValue(math.max(min, cur - SCROLL_STEP))
|
||||
else
|
||||
this:SetVerticalScroll(math.min(maxVal, cur + SCROLL_STEP))
|
||||
this:SetValue(math.min(max, cur + SCROLL_STEP))
|
||||
end
|
||||
end)
|
||||
|
||||
-- Mouse wheel on list area
|
||||
local function OnListMouseWheel()
|
||||
local cur = scrollBar:GetValue()
|
||||
local min, max = scrollBar:GetMinMaxValues()
|
||||
if arg1 > 0 then
|
||||
scrollBar:SetValue(math.max(min, cur - SCROLL_STEP))
|
||||
else
|
||||
scrollBar:SetValue(math.min(max, cur + SCROLL_STEP))
|
||||
end
|
||||
end
|
||||
|
||||
listBg:EnableMouseWheel(true)
|
||||
listBg:SetScript("OnMouseWheel", OnListMouseWheel)
|
||||
|
||||
listScroll:EnableMouseWheel(true)
|
||||
listScroll:SetScript("OnMouseWheel", OnListMouseWheel)
|
||||
|
||||
listScroll.content = listContent
|
||||
MainFrame.listScroll = listScroll
|
||||
MainFrame.listBg = listBg
|
||||
MainFrame.scrollBar = scrollBar
|
||||
|
||||
for i = 1, MAX_ROWS do
|
||||
local row = CreateListRow(listContent, i)
|
||||
@@ -968,24 +1135,24 @@ function TUI:Initialize()
|
||||
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])
|
||||
-- Right side: Detail panel
|
||||
local detailPanel = CreateFrame("Frame", nil, MainFrame)
|
||||
detailPanel:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", SIDE_PAD + LIST_W + SIDE_PAD, -listTop)
|
||||
detailPanel:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, BOTTOM_H + 4)
|
||||
detailPanel: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 },
|
||||
})
|
||||
detailPanel:SetBackdropColor(0, 0, 0, 0.3)
|
||||
detailPanel:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], 0.5)
|
||||
MainFrame.detail = detailPanel
|
||||
|
||||
-- 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)
|
||||
-- Icon in detail panel
|
||||
local dIconFrame = CreateFrame("Frame", nil, detailPanel)
|
||||
dIconFrame:SetWidth(48); dIconFrame:SetHeight(48)
|
||||
dIconFrame:SetPoint("TOPLEFT", detailPanel, "TOPLEFT", 10, -10)
|
||||
dIconFrame:SetBackdrop({
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
@@ -995,55 +1162,61 @@ function TUI:Initialize()
|
||||
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
|
||||
detailPanel.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
|
||||
detailPanel.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)
|
||||
-- Name next to icon
|
||||
local dNameFS = detailPanel:CreateFontString(nil, "OVERLAY")
|
||||
dNameFS:SetFont(GetFont(), 14, "OUTLINE")
|
||||
dNameFS:SetPoint("TOPLEFT", dIconFrame, "TOPRIGHT", 8, -4)
|
||||
dNameFS:SetPoint("RIGHT", detailPanel, "RIGHT", -10, 0)
|
||||
dNameFS:SetJustifyH("LEFT")
|
||||
dNameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
|
||||
detail.nameFS = dNameFS
|
||||
detailPanel.nameFS = dNameFS
|
||||
|
||||
local dSubFS = detail:CreateFontString(nil, "OVERLAY")
|
||||
-- Subtext (rank)
|
||||
local dSubFS = detailPanel: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
|
||||
detailPanel.subFS = dSubFS
|
||||
|
||||
local dReqFS = detail:CreateFontString(nil, "OVERLAY")
|
||||
-- Requirements
|
||||
local dReqFS = detailPanel:CreateFontString(nil, "OVERLAY")
|
||||
dReqFS:SetFont(GetFont(), 11, "OUTLINE")
|
||||
dReqFS:SetPoint("TOPLEFT", dIconFrame, "BOTTOMLEFT", 0, -6)
|
||||
dReqFS:SetPoint("RIGHT", detail, "RIGHT", -4, 0)
|
||||
dReqFS:SetPoint("TOPLEFT", dIconFrame, "BOTTOMLEFT", 0, -10)
|
||||
dReqFS:SetPoint("RIGHT", detailPanel, "RIGHT", -10, 0)
|
||||
dReqFS:SetJustifyH("LEFT")
|
||||
detail.reqFS = dReqFS
|
||||
detailPanel.reqFS = dReqFS
|
||||
|
||||
local dInfoFS = detail:CreateFontString(nil, "OVERLAY")
|
||||
-- Spell info
|
||||
local dInfoFS = detailPanel:CreateFontString(nil, "OVERLAY")
|
||||
dInfoFS:SetFont(GetFont(), 11)
|
||||
dInfoFS:SetPoint("TOPLEFT", dReqFS, "BOTTOMLEFT", 0, -4)
|
||||
dInfoFS:SetPoint("RIGHT", detail, "RIGHT", -4, 0)
|
||||
dInfoFS:SetPoint("TOPLEFT", dReqFS, "BOTTOMLEFT", 0, -6)
|
||||
dInfoFS:SetPoint("RIGHT", detailPanel, "RIGHT", -10, 0)
|
||||
dInfoFS:SetJustifyH("LEFT")
|
||||
dInfoFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
|
||||
detail.infoFS = dInfoFS
|
||||
detailPanel.infoFS = dInfoFS
|
||||
|
||||
local descDivider = detail:CreateTexture(nil, "ARTWORK")
|
||||
-- Divider line
|
||||
local descDivider = detailPanel: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
|
||||
descDivider:SetPoint("TOPLEFT", dInfoFS, "BOTTOMLEFT", 0, -8)
|
||||
descDivider:SetPoint("RIGHT", detailPanel, "RIGHT", -10, 0)
|
||||
descDivider:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], 0.5)
|
||||
detailPanel.descDivider = descDivider
|
||||
|
||||
local descScroll = CreateFrame("ScrollFrame", nil, detail)
|
||||
descScroll:SetPoint("TOPLEFT", descDivider, "BOTTOMLEFT", 0, -4)
|
||||
descScroll:SetPoint("BOTTOMRIGHT", detail, "BOTTOMRIGHT", -4, 2)
|
||||
-- Description scroll area
|
||||
local descScroll = CreateFrame("ScrollFrame", nil, detailPanel)
|
||||
descScroll:SetPoint("TOPLEFT", descDivider, "BOTTOMLEFT", 0, -6)
|
||||
descScroll:SetPoint("BOTTOMRIGHT", detailPanel, "BOTTOMRIGHT", -10, 8)
|
||||
descScroll:EnableMouseWheel(true)
|
||||
descScroll:SetScript("OnMouseWheel", function()
|
||||
local cur = this:GetVerticalScroll()
|
||||
@@ -1054,20 +1227,20 @@ function TUI:Initialize()
|
||||
this:SetVerticalScroll(math.min(maxVal, cur + 14))
|
||||
end
|
||||
end)
|
||||
detail.descScroll = descScroll
|
||||
detailPanel.descScroll = descScroll
|
||||
|
||||
local descContent = CreateFrame("Frame", nil, descScroll)
|
||||
descContent:SetWidth(CONTENT_W - 8)
|
||||
descContent:SetWidth(DETAIL_W - 20)
|
||||
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:SetWidth(DETAIL_W - 20)
|
||||
dDescFS:SetJustifyH("LEFT")
|
||||
dDescFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
|
||||
detail.descFS = dDescFS
|
||||
detailPanel.descFS = dDescFS
|
||||
|
||||
-- Bottom bar
|
||||
local bottomSep = MainFrame:CreateTexture(nil, "ARTWORK")
|
||||
@@ -1125,8 +1298,9 @@ function TUI:Initialize()
|
||||
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 name = GetTrainerServiceInfo(i)
|
||||
local cat = GetVerifiedCategory(i)
|
||||
if name and cat == "available" and not IsServiceHeader(i) then
|
||||
local ok, cost = pcall(GetTrainerServiceCost, i)
|
||||
if ok and cost and gold >= cost then
|
||||
pcall(BuyTrainerService, i)
|
||||
@@ -1192,11 +1366,15 @@ function TUI:Initialize()
|
||||
end
|
||||
|
||||
selectedIndex = nil
|
||||
currentFilter = "all"
|
||||
currentFilter = "available"
|
||||
collapsedCats = {}
|
||||
qualityCache = {}
|
||||
|
||||
-- Cache tradeskill trainer status once (avoid repeated API calls)
|
||||
isTradeskillTrainerCached = IsTradeskillTrainer and IsTradeskillTrainer() or false
|
||||
|
||||
local npcName = UnitName("npc") or "训练师"
|
||||
if IsTradeskillTrainer and IsTradeskillTrainer() then
|
||||
if isTradeskillTrainerCached then
|
||||
npcName = npcName .. " - 专业训练"
|
||||
end
|
||||
MainFrame.npcNameFS:SetText(npcName)
|
||||
|
||||
Reference in New Issue
Block a user