Files
Nanami-UI/QuestLogSkin.lua
2026-03-16 13:48:46 +08:00

1309 lines
48 KiB
Lua

--------------------------------------------------------------------------------
-- Nanami-UI: Quest Log Skin (QuestLogSkin.lua)
-- Reskins the default QuestLogFrame with Nanami-UI styled interface
-- Maintains full compatibility with pfquest and other quest addons
--------------------------------------------------------------------------------
SFrames = SFrames or {}
SFrames.QuestLogSkin = {}
local QLS = SFrames.QuestLogSkin
SFramesDB = SFramesDB or {}
--------------------------------------------------------------------------------
-- Theme (Pink Cat-Paw)
--------------------------------------------------------------------------------
local T = SFrames.Theme:Extend({
objectiveComplete = { 0.45, 0.90, 0.45 },
objectiveIncomplete = { 0.85, 0.78, 0.70 },
tagElite = { 1.0, 0.65, 0.20 },
tagDungeon = { 0.40, 0.70, 1.0 },
tagRaid = { 0.70, 0.45, 1.0 },
tagPvP = { 0.90, 0.30, 0.30 },
tagComplete = { 0.40, 0.90, 0.40 },
})
--------------------------------------------------------------------------------
-- Layout
--------------------------------------------------------------------------------
local FRAME_W = 595
local LIST_W = 250
local DETAIL_PAD = 8
local HEADER_H = 30
local BOTTOM_H = 36
local INNER_PAD = 6
local SCROLL_W = 14
local LIST_BTN_W = LIST_W - INNER_PAD * 2 - SCROLL_W - 4
--------------------------------------------------------------------------------
-- State
--------------------------------------------------------------------------------
local skinApplied = false
--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------
local function GetFont()
if SFrames and SFrames.GetFont then return SFrames:GetFont() end
return "Fonts\\ARIALN.TTF"
end
local function SetRoundBackdrop(frame, bgColor, borderColor)
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 },
})
local bg = bgColor or T.panelBg
local bd = borderColor or T.panelBorder
frame:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 1)
frame:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 1)
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:SetFrameLevel(math.max(parent:GetFrameLevel() - 1, 0))
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.6)
s:SetBackdropBorderColor(0, 0, 0, 0.45)
return s
end
local function StripTextures(frame)
if not frame then return end
local regions = { frame:GetRegions() }
for i = 1, table.getn(regions) do
local region = regions[i]
if region and region.SetTexture and not region._nanamiKeep then
local drawLayer = region.GetDrawLayer and region:GetDrawLayer()
if drawLayer == "BACKGROUND" or drawLayer == "BORDER" or drawLayer == "ARTWORK" then
region:SetTexture(nil)
region:SetAlpha(0)
region:Hide()
end
end
end
end
local function StripAllTextures(frame)
if not frame then return end
local regions = { frame:GetRegions() }
for i = 1, table.getn(regions) do
local region = regions[i]
if region and region.SetTexture and not region._nanamiKeep then
region:SetTexture(nil)
region:SetAlpha(0)
region:Hide()
end
end
end
local function StripNormalTexture(btn)
if not btn or not btn.GetNormalTexture then return end
local nt = btn:GetNormalTexture()
if nt then nt:SetTexture(nil); nt:SetAlpha(0) end
end
local function StripPushedTexture(btn)
if not btn or not btn.GetPushedTexture then return end
local pt = btn:GetPushedTexture()
if pt then pt:SetTexture(nil); pt:SetAlpha(0) end
end
local function StripHighlightTexture(btn)
if not btn or not btn.GetHighlightTexture then return end
local ht = btn:GetHighlightTexture()
if ht then ht:SetTexture(nil); ht:SetAlpha(0) end
end
local function StripDisabledTexture(btn)
if not btn or not btn.GetDisabledTexture then return end
local dt = btn:GetDisabledTexture()
if dt then dt:SetTexture(nil); dt:SetAlpha(0) end
end
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 LINK_QUALITY_MAP = {
["9d9d9d"] = 0, ["ffffff"] = 1, ["1eff00"] = 2,
["0070dd"] = 3, ["a335ee"] = 4, ["ff8000"] = 5,
["e6cc80"] = 6,
}
local function QualityFromLink(link)
if not link then return nil end
local _, _, hex = string.find(link, "|c(%x%x%x%x%x%x%x%x)|")
if hex and string.len(hex) == 8 then
local color = string.lower(string.sub(hex, 3, 8))
return LINK_QUALITY_MAP[color]
end
return nil
end
local function GetQuestGreenRange()
local level = UnitLevel("player") or 60
if level <= 5 then return 0 end
if level <= 39 then return math.floor(level / 10) + 5 end
return math.floor(level / 5) + 1 + 8
end
local function GetDiffColor(level)
local pLvl = UnitLevel("player") or 60
local diff = (level or pLvl) - pLvl
if diff >= 5 then return 1, 0.10, 0.10
elseif diff >= 3 then return 1, 0.50, 0.25
elseif diff >= -2 then return 1, 1, 0
elseif diff >= -GetQuestGreenRange() then return 0.25, 0.75, 0.25
else return 0.50, 0.50, 0.50
end
end
--------------------------------------------------------------------------------
-- Scrollbar Skinning
--------------------------------------------------------------------------------
local function SkinScrollBar(scrollBarName)
local sb = _G[scrollBarName]
if not sb then return end
StripAllTextures(sb)
local track = sb:CreateTexture(nil, "BACKGROUND")
track:SetTexture("Interface\\Buttons\\WHITE8X8")
track:SetVertexColor(T.scrollTrack[1], T.scrollTrack[2], T.scrollTrack[3], T.scrollTrack[4])
track:SetWidth(6)
track:SetPoint("TOP", sb, "TOP", 0, -16)
track:SetPoint("BOTTOM", sb, "BOTTOM", 0, 16)
local thumb = sb:GetThumbTexture()
if thumb then
thumb:SetTexture("Interface\\Buttons\\WHITE8X8")
thumb:SetVertexColor(T.scrollThumb[1], T.scrollThumb[2], T.scrollThumb[3], T.scrollThumb[4])
thumb:SetWidth(6)
thumb:SetHeight(40)
end
for _, suffix in ipairs({"ScrollUpButton", "ScrollDownButton"}) do
local b = _G[scrollBarName .. suffix]
if b then
StripAllTextures(b)
StripNormalTexture(b)
StripPushedTexture(b)
StripDisabledTexture(b)
StripHighlightTexture(b)
b:SetWidth(6)
b:SetHeight(6)
end
end
end
--------------------------------------------------------------------------------
-- Action Button Skinning
--------------------------------------------------------------------------------
local btnDisabledBg = { 0.08, 0.04, 0.06, 0.60 }
local btnDisabledBd = { 0.22, 0.14, 0.18, 0.40 }
local btnDisabledText = { 0.35, 0.25, 0.30 }
local function RefreshButtonDisabledLook(btn)
if not btn then return end
local disabled = false
if btn.IsEnabled then
local ok, result = pcall(btn.IsEnabled, btn)
if ok then disabled = not result end
end
if disabled then
SetRoundBackdrop(btn, btnDisabledBg, btnDisabledBd)
if btn.GetFontString then
local fs = btn:GetFontString()
if fs then fs:SetTextColor(btnDisabledText[1], btnDisabledText[2], btnDisabledText[3]) end
end
else
SetRoundBackdrop(btn, T.btnBg, T.btnBorder)
if btn.GetFontString then
local fs = btn:GetFontString()
if fs then fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3]) end
end
end
end
local function SkinActionButton(btn)
if not btn or btn._nanamiActionSkinned then return end
btn._nanamiActionSkinned = true
StripAllTextures(btn)
StripNormalTexture(btn)
StripPushedTexture(btn)
StripHighlightTexture(btn)
SetRoundBackdrop(btn, T.btnBg, T.btnBorder)
if btn.GetFontString then
local fs = btn:GetFontString()
if fs then
fs:SetFont(GetFont(), 11, "OUTLINE")
fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
end
end
local origEnter = btn:GetScript("OnEnter")
btn:SetScript("OnEnter", function()
local disabled = false
if this.IsEnabled then
local ok, r = pcall(this.IsEnabled, this)
if ok then disabled = not r end
end
if disabled then
if origEnter then origEnter() end
return
end
SetRoundBackdrop(this, T.btnHoverBg, T.btnHoverBd)
if this.GetFontString then
local tfs = this:GetFontString()
if tfs then tfs:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3]) end
end
if origEnter then origEnter() end
end)
local origLeave = btn:GetScript("OnLeave")
btn:SetScript("OnLeave", function()
local disabled = false
if this.IsEnabled then
local ok, r = pcall(this.IsEnabled, this)
if ok then disabled = not r end
end
if disabled then
RefreshButtonDisabledLook(this)
if origLeave then origLeave() end
return
end
SetRoundBackdrop(this, T.btnBg, T.btnBorder)
if this.GetFontString then
local tfs = this:GetFontString()
if tfs then tfs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3]) end
end
if origLeave then origLeave() end
end)
btn:SetScript("OnMouseDown", function()
local disabled = false
if this.IsEnabled then
local ok, r = pcall(this.IsEnabled, this)
if ok then disabled = not r end
end
if disabled then return end
SetRoundBackdrop(this, T.btnDownBg, T.btnBorder)
end)
btn:SetScript("OnMouseUp", function()
local disabled = false
if this.IsEnabled then
local ok, r = pcall(this.IsEnabled, this)
if ok then disabled = not r end
end
if disabled then return end
SetRoundBackdrop(this, T.btnHoverBg, T.btnHoverBd)
end)
end
--------------------------------------------------------------------------------
-- Reward Item Skinning
--------------------------------------------------------------------------------
local function SkinRewardItem(btn)
if not btn or btn._nanamiSkinned then return end
btn._nanamiSkinned = true
local bname = btn:GetName() or ""
local iconTex = _G[bname .. "IconTexture"]
if iconTex then iconTex._nanamiKeep = true end
StripAllTextures(btn)
StripNormalTexture(btn)
SetRoundBackdrop(btn, T.rewardBg, T.rewardBorder)
if iconTex then
iconTex:SetTexCoord(0.08, 0.92, 0.08, 0.92)
iconTex:ClearAllPoints()
iconTex:SetPoint("LEFT", btn, "LEFT", 4, 0)
iconTex:SetWidth(30); iconTex:SetHeight(30)
iconTex:SetAlpha(1)
iconTex:Show()
end
local nameFrame = _G[bname .. "NameFrame"]
if nameFrame then nameFrame:SetTexture(nil); nameFrame:SetAlpha(0) end
local nameText = _G[bname .. "Name"]
if nameText then
nameText:SetFont(GetFont(), 11, "OUTLINE")
nameText:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
end
local countText = _G[bname .. "Count"]
if countText then countText:SetFont(GetFont(), 10, "OUTLINE") end
end
--------------------------------------------------------------------------------
-- Quest Log Item Quality Coloring
--------------------------------------------------------------------------------
local function GetQuestLogItemQuality(itemType, index)
if itemType == "choice" and GetQuestLogChoiceInfo then
local ok, name, tex, num, q, usable = pcall(GetQuestLogChoiceInfo, index)
if ok and q and q > 0 then return q end
elseif itemType == "reward" and GetQuestLogRewardInfo then
local ok, name, tex, num, q, usable = pcall(GetQuestLogRewardInfo, index)
if ok and q and q > 0 then return q end
end
if GetQuestLogItemLink then
local ok, link = pcall(GetQuestLogItemLink, itemType, index)
if ok and link then
local q = QualityFromLink(link)
if q then return q end
if GetItemInfo then
local ok2, _, _, iq = pcall(GetItemInfo, link)
if ok2 and iq and iq > 0 then return iq end
end
end
end
return nil
end
local function ApplyQuestLogItemQuality()
local choiceIdx = 0
local rewardIdx = 0
for i = 1, 10 do
local item = _G["QuestLogItem" .. i]
if not item or not item:IsVisible() then break end
local bname = item:GetName() or ""
local nameText = _G[bname .. "Name"]
if not nameText then break end
local itemType = item.type
local idx = 0
if itemType == "choice" then
choiceIdx = choiceIdx + 1
idx = choiceIdx
elseif itemType == "reward" then
rewardIdx = rewardIdx + 1
idx = rewardIdx
else
break
end
local quality = GetQuestLogItemQuality(itemType, idx)
local qc = quality and QUALITY_COLORS[quality]
if qc and quality >= 2 then
nameText:SetTextColor(qc[1], qc[2], qc[3])
item:SetBackdropBorderColor(qc[1], qc[2], qc[3], 0.8)
elseif qc and quality == 1 then
nameText:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
item:SetBackdropBorderColor(T.rewardBorder[1], T.rewardBorder[2], T.rewardBorder[3], T.rewardBorder[4])
else
nameText:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
item:SetBackdropBorderColor(T.rewardBorder[1], T.rewardBorder[2], T.rewardBorder[3], T.rewardBorder[4])
end
end
end
--------------------------------------------------------------------------------
-- Tag Color for elite/dungeon/raid/pvp/complete
--------------------------------------------------------------------------------
local function GetTagColor(tagText)
if not tagText or tagText == "" then return nil end
local t = string.lower(tagText)
if string.find(t, "elite") or string.find(t, "精英") then
return T.tagElite
elseif string.find(t, "dungeon") or string.find(t, "地下城") then
return T.tagDungeon
elseif string.find(t, "raid") or string.find(t, "团队") then
return T.tagRaid
elseif string.find(t, "pvp") then
return T.tagPvP
elseif string.find(t, "complete") or string.find(t, "完成") then
return T.tagComplete
end
return T.dimText
end
--------------------------------------------------------------------------------
-- Main Frame Skinning
--------------------------------------------------------------------------------
local function SkinMainFrame()
local f = QuestLogFrame
if not f then return end
-- Resize frame to remove excess right-side space
f:SetWidth(FRAME_W)
StripTextures(f)
SetRoundBackdrop(f, T.panelBg, T.panelBorder)
CreateShadow(f)
for _, pName in ipairs({"QuestLogFramePortrait", "QuestLogPortrait"}) do
local p = _G[pName]
if p then p:SetTexture(nil); p:SetAlpha(0); p:Hide() end
end
local titleText = QuestLogTitleText
if titleText then
titleText:SetFont(GetFont(), 14, "OUTLINE")
titleText:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
titleText:ClearAllPoints()
titleText:SetPoint("LEFT", f, "TOPLEFT", 14, -15)
end
-- Quest count to top-right
local questCount = QuestLogQuestCount
if questCount then
questCount:SetFont(GetFont(), 11, "OUTLINE")
questCount:SetTextColor(T.countText[1], T.countText[2], T.countText[3])
questCount:ClearAllPoints()
questCount:SetPoint("RIGHT", f, "TOPRIGHT", -30, -15)
end
local closeBtn = _G["QuestLogFrameCloseButton"]
if closeBtn then
closeBtn:ClearAllPoints()
closeBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -2, -2)
closeBtn:SetWidth(22); closeBtn:SetHeight(22)
closeBtn:SetFrameLevel(f:GetFrameLevel() + 10)
end
-- Make frame draggable and respond to ESC
if UIPanelWindows then
UIPanelWindows["QuestLogFrame"] = nil
end
table.insert(UISpecialFrames, "QuestLogFrame")
f:SetMovable(true)
f:EnableMouse(true)
f:SetClampedToScreen(true)
f:RegisterForDrag("LeftButton")
f:SetScript("OnDragStart", function() this:StartMoving() end)
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
-- Header separator
local sep = f:CreateTexture(nil, "ARTWORK")
sep:SetTexture("Interface\\Buttons\\WHITE8X8")
sep:SetHeight(1)
sep:SetPoint("TOPLEFT", f, "TOPLEFT", 8, -HEADER_H)
sep:SetPoint("TOPRIGHT", f, "TOPRIGHT", -8, -HEADER_H)
sep:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4])
-- Bottom separator
local bsep = f:CreateTexture(nil, "ARTWORK")
bsep:SetTexture("Interface\\Buttons\\WHITE8X8")
bsep:SetHeight(1)
bsep:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 8, BOTTOM_H)
bsep:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -8, BOTTOM_H)
bsep:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4])
-- Detect and preserve Turtle WoW translate / ? buttons
-- Also find any unrecognized buttons and ensure they remain visible
local knownPrefixes = {
"QuestLogTitle", "QuestLogFrame", "QuestLogCollapseAll",
"QuestLogTrack", "QuestFramePush", "pfQuest",
}
local function isKnownButton(child)
if child._nanamiPfSkinned or child._nanamiBottomBtn
or child._nanamiActionSkinned then return true end
local cname = child:GetName() or ""
if cname == "" then return false end
for ki = 1, table.getn(knownPrefixes) do
if string.find(cname, knownPrefixes[ki]) then return true end
end
return false
end
f._skinExtraButtons = function()
local searchParents = {}
table.insert(searchParents, f)
local dc = _G["QuestLogDetailScrollFrameChildFrame"]
or _G["QuestLogDetailScrollFrameChild"]
if dc then table.insert(searchParents, dc) end
local ds = QuestLogDetailScrollFrame
if ds then table.insert(searchParents, ds) end
local detailLevel = (f._nanamiDetailPanel and f._nanamiDetailPanel:GetFrameLevel()) or (f:GetFrameLevel() + 2)
for pi = 1, table.getn(searchParents) do
local parent = searchParents[pi]
if parent and parent.GetChildren then
local children = { parent:GetChildren() }
for ci = 1, table.getn(children) do
local child = children[ci]
if child and child.GetObjectType then
local objType = child:GetObjectType()
if objType == "Button" and not isKnownButton(child) then
-- Ensure button is visible above detailPanel
if child:GetFrameLevel() <= detailLevel then
child:SetFrameLevel(detailLevel + 3)
end
child:Show()
SkinPfQuestButton(child)
end
end
end
end
end
-- Also search for globally named Turtle WoW translate buttons
for _, gname in ipairs({
"QuestTranslateButton", "QuestLogTranslateButton",
"QuestInfoTranslateButton", "TurtleTranslateButton",
"QuestTranslationButton", "QuestHelpButton",
}) do
local gb = _G[gname]
if gb and gb.GetObjectType and not gb._nanamiPfSkinned then
gb:Show()
if gb:GetFrameLevel() <= detailLevel then
gb:SetFrameLevel(detailLevel + 3)
end
SkinPfQuestButton(gb)
end
end
end
end
--------------------------------------------------------------------------------
-- Quest List Panel Skinning
--------------------------------------------------------------------------------
local function SkinQuestList()
local listFrame = QuestLogListScrollFrame
if not listFrame then return end
local f = QuestLogFrame
local listPanel = CreateFrame("Frame", nil, f)
listPanel:SetPoint("TOPLEFT", f, "TOPLEFT", DETAIL_PAD, -(HEADER_H + 2))
listPanel:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", DETAIL_PAD, BOTTOM_H + 2)
listPanel:SetWidth(LIST_W)
SetRoundBackdrop(listPanel, T.listBg, T.listBorder)
listPanel:SetFrameLevel(f:GetFrameLevel() + 1)
f._nanamiListPanel = listPanel
listFrame:ClearAllPoints()
listFrame:SetPoint("TOPLEFT", listPanel, "TOPLEFT", INNER_PAD, -INNER_PAD)
listFrame:SetPoint("BOTTOMRIGHT", listPanel, "BOTTOMRIGHT", -(SCROLL_W + INNER_PAD), 4)
listFrame:SetWidth(LIST_W - INNER_PAD * 2 - SCROLL_W)
StripTextures(listFrame)
SkinScrollBar("QuestLogListScrollFrameScrollBar")
local collapseAll = QuestLogCollapseAllButton
if collapseAll then
StripNormalTexture(collapseAll)
StripPushedTexture(collapseAll)
StripHighlightTexture(collapseAll)
end
local highlight = _G["QuestLogHighlightFrame"]
if highlight then
StripAllTextures(highlight)
highlight:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
tile = true, tileSize = 16,
insets = { left = 0, right = 0, top = 0, bottom = 0 },
})
highlight:SetBackdropColor(T.questSelected[1], T.questSelected[2], T.questSelected[3], T.questSelected[4])
local hlBar = highlight:CreateTexture(nil, "OVERLAY")
hlBar:SetTexture("Interface\\Buttons\\WHITE8X8")
hlBar:SetWidth(3)
hlBar:SetPoint("TOPLEFT", highlight, "TOPLEFT", 0, 0)
hlBar:SetPoint("BOTTOMLEFT", highlight, "BOTTOMLEFT", 0, 0)
hlBar:SetVertexColor(T.questSelBar[1], T.questSelBar[2], T.questSelBar[3], T.questSelBar[4])
end
end
--------------------------------------------------------------------------------
-- Quest Detail Panel Skinning
--------------------------------------------------------------------------------
local function SkinQuestDetail()
local detailFrame = QuestLogDetailScrollFrame
if not detailFrame then return end
local f = QuestLogFrame
local DETAIL_W = FRAME_W - LIST_W - DETAIL_PAD * 2 - 4
local detailPanel = CreateFrame("Frame", nil, f)
detailPanel:SetPoint("TOPLEFT", f, "TOPLEFT", LIST_W + DETAIL_PAD + 4, -(HEADER_H + 2))
detailPanel:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -DETAIL_PAD, BOTTOM_H + 2)
SetRoundBackdrop(detailPanel, T.detailBg, T.detailBorder)
detailPanel:SetFrameLevel(f:GetFrameLevel() + 1)
f._nanamiDetailPanel = detailPanel
detailFrame:ClearAllPoints()
detailFrame:SetPoint("TOPLEFT", detailPanel, "TOPLEFT", INNER_PAD, -INNER_PAD)
detailFrame:SetPoint("BOTTOMRIGHT", detailPanel, "BOTTOMRIGHT", -(SCROLL_W + INNER_PAD), INNER_PAD)
detailFrame:SetFrameLevel(detailPanel:GetFrameLevel() + 2)
StripTextures(detailFrame)
SkinScrollBar("QuestLogDetailScrollFrameScrollBar")
local contentW = DETAIL_W - INNER_PAD * 2 - SCROLL_W - 6
local detailChild = _G["QuestLogDetailScrollFrameChildFrame"]
or _G["QuestLogDetailScrollFrameChild"]
if detailChild and contentW > 100 then
detailChild:SetWidth(contentW)
end
if QuestLogNoQuestsText then
QuestLogNoQuestsText:SetFont(GetFont(), 13)
QuestLogNoQuestsText:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
end
if EmptyQuestLogFrame then
StripTextures(EmptyQuestLogFrame)
local et = _G["EmptyQuestLogFrameText"]
if et then
et:SetFont(GetFont(), 13)
et:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
end
end
end
--------------------------------------------------------------------------------
-- Bottom Buttons Skinning
--------------------------------------------------------------------------------
local function SkinBottomButtons()
local f = QuestLogFrame
local btnW = math.floor((FRAME_W - 40) / 3)
if btnW > 120 then btnW = 120 end
if btnW < 80 then btnW = 80 end
local abandonBtn = QuestLogFrameAbandonButton
if abandonBtn then
SkinActionButton(abandonBtn)
abandonBtn:SetHeight(24)
abandonBtn:ClearAllPoints()
abandonBtn:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 12, 6)
abandonBtn:SetWidth(btnW)
abandonBtn._nanamiBottomBtn = true
end
local shareBtn = _G["QuestLogFramePushQuestButton"] or _G["QuestFramePushQuestButton"]
if shareBtn then
SkinActionButton(shareBtn)
shareBtn:SetHeight(24)
shareBtn:ClearAllPoints()
shareBtn:SetPoint("BOTTOM", f, "BOTTOM", 0, 6)
shareBtn:SetWidth(btnW)
shareBtn._nanamiBottomBtn = true
shareBtn._nanamiOrigText = nil
if shareBtn.GetFontString then
local fs = shareBtn:GetFontString()
if fs then shareBtn._nanamiOrigText = fs:GetText() end
end
shareBtn._nanamiRefreshShare = function(sb)
if not sb then return end
local canShare = GetQuestLogPushable and GetQuestLogPushable()
local fs = sb.GetFontString and sb:GetFontString()
if not canShare then
SetRoundBackdrop(sb, btnDisabledBg, btnDisabledBd)
if fs then
fs:SetTextColor(btnDisabledText[1], btnDisabledText[2], btnDisabledText[3])
end
else
SetRoundBackdrop(sb, T.btnBg, T.btnBorder)
if fs then
fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
end
end
end
shareBtn._nanamiRefreshShare(shareBtn)
local sharePoll = CreateFrame("Frame", nil, shareBtn)
sharePoll._t = 0
sharePoll:SetScript("OnUpdate", function()
this._t = (this._t or 0) + (arg1 or 0)
if this._t < 1.0 then return end
this._t = 0
local sb = this:GetParent()
if sb and sb._nanamiRefreshShare then sb._nanamiRefreshShare(sb) end
end)
end
-- Search for exit button by multiple names and text content
local exitBtn = nil
for _, eName in ipairs({
"QuestLogFrameExitButton", "QuestLogExitButton",
"QuestLogCancelButton", "QuestLogCloseButton",
"QuestLogFrameCancelButton",
}) do
if _G[eName] then exitBtn = _G[eName]; break end
end
-- Fallback: find any bottom button with "退出"/"Exit"/"Close" text
if not exitBtn then
local children = { f:GetChildren() }
for ci = 1, table.getn(children) do
local child = children[ci]
if child and child.GetObjectType and child:GetObjectType() == "Button"
and child.GetFontString then
local cfs = child:GetFontString()
if cfs then
local txt = cfs:GetText()
if txt and (string.find(txt, "退出") or string.find(txt, "Exit")
or string.find(txt, "Close") or string.find(txt, "关闭")) then
exitBtn = child
break
end
end
end
end
end
if exitBtn then
SkinActionButton(exitBtn)
exitBtn:SetHeight(24)
exitBtn:ClearAllPoints()
exitBtn:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -12, 6)
exitBtn:SetWidth(btnW)
exitBtn._nanamiBottomBtn = true
end
-- Also skin any other unskinned bottom-area buttons (Turtle WoW additions)
local children = { f:GetChildren() }
for ci = 1, table.getn(children) do
local child = children[ci]
if child and child.GetObjectType and child:GetObjectType() == "Button"
and not child._nanamiBottomBtn and not child._nanamiPfSkinned
and not child._nanamiActionSkinned then
-- Check if this button is near the bottom of the frame
local point = child.GetPoint and child:GetPoint()
if point and (point == "BOTTOMLEFT" or point == "BOTTOMRIGHT" or point == "BOTTOM") then
if child.GetFontString then
local cfs = child:GetFontString()
if cfs then
local txt = cfs:GetText()
if txt and txt ~= "" then
SkinActionButton(child)
child._nanamiBottomBtn = true
end
end
end
end
end
end
end
--------------------------------------------------------------------------------
-- Quest List Entries Restyling
--------------------------------------------------------------------------------
local function GetHighlightAnchorBtn()
local hl = _G["QuestLogHighlightFrame"]
if hl and hl.IsVisible and hl:IsVisible() and hl.GetPoint then
local ok, _, anchor = pcall(hl.GetPoint, hl)
if ok and anchor then return anchor end
end
return nil
end
local function StyleQuestListEntries()
local numEntries, numQuests = 0, 0
if GetNumQuestLogEntries then
numEntries, numQuests = GetNumQuestLogEntries()
end
local questsDisplayed = QUESTS_DISPLAYED or 22
local hlBtn = GetHighlightAnchorBtn()
for i = 1, questsDisplayed do
local btn = _G["QuestLogTitle" .. i]
if not btn then break end
if not btn._nanamiSkinned then
StripPushedTexture(btn)
local nt = _G["QuestLogTitle" .. i .. "NormalText"]
if nt then nt:SetFont(GetFont(), 11) end
local tg = _G["QuestLogTitle" .. i .. "Tag"]
if tg then tg:SetFont(GetFont(), 10, "OUTLINE") end
local origHL = btn:GetHighlightTexture()
if origHL then
origHL:SetTexture("Interface\\Buttons\\WHITE8X8")
origHL:SetVertexColor(T.questHover[1], T.questHover[2], T.questHover[3], T.questHover[4])
origHL:SetAllPoints(btn)
origHL:SetBlendMode("ADD")
end
btn._nanamiCollapse = btn:CreateFontString(nil, "OVERLAY")
btn._nanamiCollapse:SetFont(GetFont(), 12, "OUTLINE")
btn._nanamiCollapse:SetPoint("LEFT", btn, "LEFT", 2, 0)
btn._nanamiCollapse:SetTextColor(T.collapseIcon[1], T.collapseIcon[2], T.collapseIcon[3])
btn._nanamiCollapse:Hide()
btn._nanamiTrackBar = btn:CreateTexture(nil, "OVERLAY")
btn._nanamiTrackBar:SetTexture("Interface\\Buttons\\WHITE8X8")
btn._nanamiTrackBar:SetWidth(3)
btn._nanamiTrackBar:SetPoint("TOPRIGHT", btn, "TOPRIGHT", 0, 0)
btn._nanamiTrackBar:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 0, 0)
btn._nanamiTrackBar:SetVertexColor(T.trackBar[1], T.trackBar[2], T.trackBar[3], 1)
btn._nanamiTrackBar:Hide()
btn._nanamiTrackLabel = btn:CreateFontString(nil, "OVERLAY")
btn._nanamiTrackLabel:SetFont(GetFont(), 9, "OUTLINE")
btn._nanamiTrackLabel:SetPoint("RIGHT", btn, "RIGHT", -6, 0)
btn._nanamiTrackLabel:SetTextColor(T.trackBar[1], T.trackBar[2], T.trackBar[3], 1)
btn._nanamiTrackLabel:Hide()
local gmFS = _G["QuestLogTitle" .. i .. "GroupMates"]
if gmFS and gmFS.SetFont then
gmFS:SetFont(GetFont(), 10, "OUTLINE")
end
btn._nanamiSkinned = true
end
btn:SetWidth(LIST_BTN_W)
local normalText = _G["QuestLogTitle" .. i .. "NormalText"]
local tagFS = _G["QuestLogTitle" .. i .. "Tag"]
if btn:IsVisible() and normalText then
normalText:SetWidth(LIST_BTN_W - 30)
local questIndex = btn.questLogIndex
local isSelected = (btn == hlBtn) and not btn.isHeader
if btn.isHeader then
normalText:SetTextColor(T.zoneHeader[1], T.zoneHeader[2], T.zoneHeader[3])
if btn._nanamiCollapse then
local collapsed = false
if btn.IsExpanded then
collapsed = not btn:IsExpanded()
elseif btn.IsCollapsed then
collapsed = btn:IsCollapsed()
end
local ntex = btn.GetNormalTexture and btn:GetNormalTexture()
if ntex then
local tex = ntex.GetTexture and ntex:GetTexture()
if tex then
tex = string.lower(tex)
if string.find(tex, "plus") or string.find(tex, "expand") then
collapsed = true
elseif string.find(tex, "minus") or string.find(tex, "collapse") then
collapsed = false
end
end
ntex:SetAlpha(0)
end
btn._nanamiCollapse:SetText(collapsed and "+" or "-")
btn._nanamiCollapse:Show()
end
if btn._nanamiTrackBar then btn._nanamiTrackBar:Hide() end
if btn._nanamiTrackLabel then btn._nanamiTrackLabel:Hide() end
if tagFS then tagFS:SetText("") end
else
local diffR, diffG, diffB
if questIndex and GetQuestLogTitle then
local _, level = GetQuestLogTitle(questIndex)
if level and level > 0 then
if GetQuestDifficultyColor then
local c = GetQuestDifficultyColor(level)
if c then diffR, diffG, diffB = c.r, c.g, c.b end
end
if not diffR then
diffR, diffG, diffB = GetDiffColor(level)
end
end
end
if diffR then
normalText:SetTextColor(diffR, diffG, diffB)
elseif btn.GetTextColor then
local cr, cg, cb = btn:GetTextColor()
if cr then normalText:SetTextColor(cr, cg, cb) end
end
if isSelected then
normalText:SetTextColor(1, 1, 1)
end
if btn._nanamiCollapse then btn._nanamiCollapse:Hide() end
local check = _G["QuestLogTitle" .. i .. "Check"]
local isTracked = check and check.IsVisible and check:IsVisible()
if btn._nanamiTrackBar then
if isTracked then btn._nanamiTrackBar:Show() else btn._nanamiTrackBar:Hide() end
end
if btn._nanamiTrackLabel then btn._nanamiTrackLabel:Hide() end
if check then
if isTracked then
check:SetVertexColor(T.trackBar[1], T.trackBar[2], T.trackBar[3], 1)
check:SetWidth(14)
check:SetHeight(14)
else
check:SetWidth(12)
check:SetHeight(12)
end
end
if tagFS then
local tagText = tagFS:GetText()
if tagText and tagText ~= "" then
if isSelected then
tagFS:SetTextColor(1, 1, 1)
elseif diffR then
tagFS:SetTextColor(diffR, diffG, diffB)
elseif btn.GetTextColor then
local cr, cg, cb = btn:GetTextColor()
if cr then tagFS:SetTextColor(cr, cg, cb) end
end
end
end
end
-- check icon is handled in the tracking section above
end
end
end
--------------------------------------------------------------------------------
-- Quest Detail Text Restyling
--------------------------------------------------------------------------------
local function StyleQuestDetailText()
local titleFS = _G["QuestLogQuestTitle"]
if titleFS then
titleFS:SetFont(GetFont(), 14, "OUTLINE")
titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
end
local descFS = _G["QuestLogQuestDescription"]
if descFS then
descFS:SetFont(GetFont(), 12)
descFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
end
local objectivesTitle = _G["QuestLogObjectivesText"]
if objectivesTitle then
objectivesTitle:SetFont(GetFont(), 12)
objectivesTitle:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3])
end
local descTitle = _G["QuestLogDescriptionTitle"]
if descTitle then
descTitle:SetFont(GetFont(), 12, "OUTLINE")
descTitle:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3])
end
for i = 1, 20 do
local objFS = _G["QuestLogObjective" .. i]
if not objFS then break end
objFS:SetFont(GetFont(), 11)
local text = objFS:GetText()
if text then
local _, _, done, needed = string.find(text, "(%d+)/(%d+)")
if done and needed and tonumber(done) >= tonumber(needed) then
objFS:SetTextColor(T.objectiveComplete[1], T.objectiveComplete[2], T.objectiveComplete[3])
else
objFS:SetTextColor(T.objectiveIncomplete[1], T.objectiveIncomplete[2], T.objectiveIncomplete[3])
end
end
end
local styledTexts = {
"QuestLogRewardTitleText", "QuestLogHeaderText",
"QuestLogItemChooseText", "QuestLogItemReceiveText",
"QuestLogSpellLearnText",
}
for _, name in ipairs(styledTexts) do
local fs = _G[name]
if fs then
fs:SetFont(GetFont(), 11, "OUTLINE")
fs:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3])
end
end
local rewardMoney = _G["QuestLogRequiredMoneyText"]
if rewardMoney then rewardMoney:SetFont(GetFont(), 11) end
local timerText = _G["QuestLogTimerText"]
if timerText then
timerText:SetFont(GetFont(), 11, "OUTLINE")
timerText:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
end
for i = 1, 10 do
local item = _G["QuestLogItem" .. i]
if item then SkinRewardItem(item) end
end
ApplyQuestLogItemQuality()
end
--------------------------------------------------------------------------------
-- pfquest Button Detection & Styling
--------------------------------------------------------------------------------
local pfquestSkinned = false
local function SkinPfQuestButton(btn)
if not btn or btn._nanamiPfSkinned then return end
btn._nanamiPfSkinned = true
StripAllTextures(btn)
StripNormalTexture(btn)
StripPushedTexture(btn)
StripHighlightTexture(btn)
SetRoundBackdrop(btn, T.btnBg, T.btnBorder)
if btn.GetFontString then
local fs = btn:GetFontString()
if fs then
fs:SetFont(GetFont(), 10, "OUTLINE")
fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
end
end
local origEnter = btn:GetScript("OnEnter")
btn:SetScript("OnEnter", function()
SetRoundBackdrop(this, T.btnHoverBg, T.btnHoverBd)
if this.GetFontString then
local tfs = this:GetFontString()
if tfs then tfs:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3]) end
end
if origEnter then origEnter() end
end)
local origLeave = btn:GetScript("OnLeave")
btn:SetScript("OnLeave", function()
SetRoundBackdrop(this, T.btnBg, T.btnBorder)
if this.GetFontString then
local tfs = this:GetFontString()
if tfs then tfs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3]) end
end
if origLeave then origLeave() end
end)
end
local function IsPfQuestButtonText(text)
if not text then return false end
return string.find(text, "show") or string.find(text, "hide")
or string.find(text, "clean") or string.find(text, "reset")
or string.find(text, "Show") or string.find(text, "Hide")
or string.find(text, "Clean") or string.find(text, "Reset")
or string.find(text, "显示") or string.find(text, "隐藏")
or string.find(text, "清空") or string.find(text, "重置")
end
local function TrySkinPfQuestButtons()
local pfBtns = {}
for _, name in ipairs({
"pfQuestShow", "pfQuestHide", "pfQuestClean", "pfQuestReset",
"pfQuest.show", "pfQuest.hide", "pfQuest.clean", "pfQuest.reset",
}) do
local btn = _G[name]
if btn then
SkinPfQuestButton(btn)
table.insert(pfBtns, btn)
end
end
if pfQuest and pfQuest.buttons then
for _, btn in pairs(pfQuest.buttons) do
if type(btn) == "table" and btn.GetObjectType then
SkinPfQuestButton(btn)
table.insert(pfBtns, btn)
end
end
end
local detailChild = _G["QuestLogDetailScrollFrameChildFrame"]
or _G["QuestLogDetailScrollFrameChild"]
if detailChild and detailChild.GetChildren then
local children = { detailChild:GetChildren() }
for idx = 1, table.getn(children) do
local child = children[idx]
if child and child.GetObjectType and child:GetObjectType() == "Button" and child.GetFontString then
local fs = child:GetFontString()
if fs then
local text = fs:GetText()
if IsPfQuestButtonText(text) then
SkinPfQuestButton(child)
table.insert(pfBtns, child)
end
end
end
end
end
-- Resize pfquest buttons to fit within the detail content area (keep original positions)
local contentW = FRAME_W - LIST_W - DETAIL_PAD * 2 - 4 - INNER_PAD * 2 - SCROLL_W - 6
local btnCount = table.getn(pfBtns)
if btnCount > 0 and contentW > 100 then
local gap = 2
local pfBtnW = math.floor((contentW - gap * (btnCount - 1)) / btnCount)
if pfBtnW < 50 then pfBtnW = 50 end
for bi = 1, btnCount do
local btn = pfBtns[bi]
if btn and btn.SetWidth then
btn:SetWidth(pfBtnW)
end
end
end
end
--------------------------------------------------------------------------------
-- Quest Tracker Button Skinning
--------------------------------------------------------------------------------
local function SkinQuestTrackButton()
for _, bname in ipairs({
"QuestLogTrack", "QuestLogFrameTrackButton", "QuestLogTrackButton",
}) do
local trackBtn = _G[bname]
if trackBtn then
trackBtn:Hide()
trackBtn:SetAlpha(0)
trackBtn:SetWidth(1)
trackBtn:SetHeight(1)
trackBtn:ClearAllPoints()
trackBtn:SetPoint("TOPLEFT", QuestLogFrame, "TOPLEFT", -9999, 0)
end
end
for _, tname in ipairs({
"QuestLogTrackText", "QuestLogTrackButtonText",
"QuestLogTrackHighlight",
}) do
local t = _G[tname]
if t then
t:Hide()
if t.SetAlpha then t:SetAlpha(0) end
end
end
end
--------------------------------------------------------------------------------
-- Hooks
--------------------------------------------------------------------------------
local function ClampHighlightWidth()
local hl = _G["QuestLogHighlightFrame"]
if hl and hl.IsVisible and hl:IsVisible() then
hl:SetWidth(LIST_BTN_W)
end
end
local function EnforceListScrollSize()
local lf = QuestLogListScrollFrame
local f = QuestLogFrame
if not lf or not f or not f._nanamiListPanel then return end
local panelH = f._nanamiListPanel:GetHeight()
if panelH and panelH > 50 then
lf:SetHeight(panelH - INNER_PAD - 4)
end
end
local function InstallHooks()
local origUpdate = QuestLog_Update
if origUpdate then
QuestLog_Update = function()
origUpdate()
if QuestLogFrame then QuestLogFrame:SetWidth(FRAME_W) end
EnforceListScrollSize()
StyleQuestListEntries()
ClampHighlightWidth()
if not pfquestSkinned then
pfquestSkinned = true
TrySkinPfQuestButtons()
end
end
end
local origSetSelection = QuestLog_SetSelection
if origSetSelection then
QuestLog_SetSelection = function(index)
origSetSelection(index)
EnforceListScrollSize()
StyleQuestListEntries()
ClampHighlightWidth()
StyleQuestDetailText()
TrySkinPfQuestButtons()
if QuestLogFrame and QuestLogFrame._skinExtraButtons then
QuestLogFrame._skinExtraButtons()
end
local sb = _G["QuestLogFramePushQuestButton"] or _G["QuestFramePushQuestButton"]
if sb and sb._nanamiRefreshShare then sb._nanamiRefreshShare(sb) end
end
end
local origUpdateDetails = QuestLog_UpdateQuestDetails
if origUpdateDetails then
QuestLog_UpdateQuestDetails = function(doNotShow)
origUpdateDetails(doNotShow)
StyleQuestDetailText()
TrySkinPfQuestButtons()
if QuestLogFrame and QuestLogFrame._skinExtraButtons then
QuestLogFrame._skinExtraButtons()
end
end
end
end
--------------------------------------------------------------------------------
-- Initialize
--------------------------------------------------------------------------------
function QLS:Initialize()
if skinApplied then return end
if not QuestLogFrame then return end
skinApplied = true
SkinMainFrame()
SkinQuestList()
SkinQuestDetail()
SkinBottomButtons()
SkinQuestTrackButton()
InstallHooks()
local origOnShow = QuestLogFrame:GetScript("OnShow")
QuestLogFrame:SetScript("OnShow", function()
if origOnShow then origOnShow() end
QuestLogFrame:SetWidth(FRAME_W)
EnforceListScrollSize()
StyleQuestListEntries()
ClampHighlightWidth()
StyleQuestDetailText()
if not pfquestSkinned then
pfquestSkinned = true
TrySkinPfQuestButtons()
end
if QuestLogFrame._skinExtraButtons then
QuestLogFrame._skinExtraButtons()
end
end)
-- Delayed pfquest + translate button detection
local pfCheck = CreateFrame("Frame", nil, QuestLogFrame)
pfCheck._e = 0; pfCheck._n = 0
pfCheck:SetScript("OnUpdate", function()
this._e = (this._e or 0) + (arg1 or 0)
if this._e < 0.5 then return end
this._e = 0
this._n = (this._n or 0) + 1
TrySkinPfQuestButtons()
if QuestLogFrame._skinExtraButtons then
QuestLogFrame._skinExtraButtons()
end
if this._n >= 8 then this:SetScript("OnUpdate", nil) end
end)
if QuestLogFrame:IsVisible() then
StyleQuestListEntries()
StyleQuestDetailText()
end
end
--------------------------------------------------------------------------------
-- Bootstrap
--------------------------------------------------------------------------------
local bootstrap = CreateFrame("Frame")
bootstrap:RegisterEvent("PLAYER_LOGIN")
bootstrap:SetScript("OnEvent", function()
if event == "PLAYER_LOGIN" then
if SFramesDB.enableQuestLogSkin == nil then
SFramesDB.enableQuestLogSkin = true
end
if SFramesDB.enableQuestLogSkin ~= false then
QLS:Initialize()
end
end
end)