403 lines
15 KiB
Lua
403 lines
15 KiB
Lua
--------------------------------------------------------------------------------
|
|
-- Nanami-UI: Book Reading UI (BookUI.lua)
|
|
-- Replaces ItemTextFrame with Nanami-UI styled interface + page flipping
|
|
--------------------------------------------------------------------------------
|
|
|
|
SFrames = SFrames or {}
|
|
SFrames.BookUI = {}
|
|
local BUI = SFrames.BookUI
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Theme (match QuestUI style)
|
|
--------------------------------------------------------------------------------
|
|
local T = SFrames.ActiveTheme
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Layout
|
|
--------------------------------------------------------------------------------
|
|
local FRAME_W = 340
|
|
local FRAME_H = 400
|
|
local HEADER_H = 34
|
|
local BOTTOM_H = 42
|
|
local SIDE_PAD = 14
|
|
local CONTENT_W = FRAME_W - SIDE_PAD * 2
|
|
local SCROLL_STEP = 40
|
|
local BODY_FONT_SIZE = 12
|
|
local BODY_LINE_SPACING = 2
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- State
|
|
--------------------------------------------------------------------------------
|
|
local MainFrame = nil
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 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 TextHeight(fs, fallback)
|
|
if not fs then return fallback or 14 end
|
|
local h = fs.GetStringHeight and fs:GetStringHeight()
|
|
if h and h > 0 then return h end
|
|
h = fs:GetHeight()
|
|
if h and h > 1 then return h end
|
|
return fallback or 14
|
|
end
|
|
|
|
local function StripItemTextHTML(text)
|
|
if not text or text == "" then return text end
|
|
if not string.find(text, "<") then return text end
|
|
|
|
text = string.gsub(text, "</?HTML>", "")
|
|
text = string.gsub(text, "</?html>", "")
|
|
text = string.gsub(text, "</?BODY>", "")
|
|
text = string.gsub(text, "</?body>", "")
|
|
text = string.gsub(text, "<H1[^>]*>", "")
|
|
text = string.gsub(text, "<h1[^>]*>", "")
|
|
text = string.gsub(text, "</H1>", "\n")
|
|
text = string.gsub(text, "</h1>", "\n")
|
|
text = string.gsub(text, "<P[^>]*>", "")
|
|
text = string.gsub(text, "<p[^>]*>", "")
|
|
text = string.gsub(text, "</P>", "\n")
|
|
text = string.gsub(text, "</p>", "\n")
|
|
text = string.gsub(text, "<BR/>", "\n")
|
|
text = string.gsub(text, "<br/>", "\n")
|
|
text = string.gsub(text, "<br>", "\n")
|
|
text = string.gsub(text, "<[^>]+>", "")
|
|
|
|
text = string.gsub(text, "^[\n%s]+", "")
|
|
text = string.gsub(text, "[\n%s]+$", "")
|
|
text = string.gsub(text, "\n\n\n+", "\n\n")
|
|
return text
|
|
end
|
|
|
|
local function IsTrue(v)
|
|
return v == true or v == 1
|
|
end
|
|
|
|
local function CreateScrollArea(parent, name)
|
|
local scroll = CreateFrame("ScrollFrame", name, parent)
|
|
local content = CreateFrame("Frame", name .. "Content", scroll)
|
|
content:SetWidth(CONTENT_W)
|
|
content:SetHeight(1)
|
|
scroll:SetScrollChild(content)
|
|
|
|
scroll:EnableMouseWheel(true)
|
|
scroll: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)
|
|
|
|
scroll.content = content
|
|
return scroll
|
|
end
|
|
|
|
local function CreateActionBtn(parent, text, w)
|
|
local btn = CreateFrame("Button", nil, parent)
|
|
btn:SetWidth(w or 90)
|
|
btn:SetHeight(28)
|
|
btn: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 },
|
|
})
|
|
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(), 11, "OUTLINE")
|
|
fs:SetPoint("CENTER", 0, 0)
|
|
fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
fs:SetText(text or "")
|
|
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])
|
|
self:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[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)
|
|
btn:SetScript("OnMouseDown", function()
|
|
if not this.disabled then
|
|
this:SetBackdropColor(T.btnDownBg[1], T.btnDownBg[2], T.btnDownBg[3], T.btnDownBg[4])
|
|
end
|
|
end)
|
|
btn:SetScript("OnMouseUp", function()
|
|
if not this.disabled then
|
|
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
|
|
end
|
|
end)
|
|
|
|
return btn
|
|
end
|
|
|
|
local function HideBlizzardItemText()
|
|
if not ItemTextFrame then return end
|
|
ItemTextFrame:SetAlpha(0)
|
|
ItemTextFrame:EnableMouse(false)
|
|
ItemTextFrame:ClearAllPoints()
|
|
ItemTextFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMRIGHT", 2000, 2000)
|
|
end
|
|
|
|
local function UpdateBookContent()
|
|
if not MainFrame then return end
|
|
|
|
local title = ItemTextGetItem and (ItemTextGetItem() or "") or ""
|
|
local text = ItemTextGetText and (ItemTextGetText() or "") or ""
|
|
if (not text or text == "") and ItemTextPageText and ItemTextPageText.GetText then
|
|
text = ItemTextPageText:GetText() or ""
|
|
end
|
|
if text and text ~= "" then
|
|
text = StripItemTextHTML(text)
|
|
text = string.gsub(text, "\n%s*\n%s*\n+", "\n\n")
|
|
end
|
|
local pageNum = ItemTextGetPage and tonumber(ItemTextGetPage()) or 1
|
|
if not pageNum or pageNum < 1 then pageNum = 1 end
|
|
|
|
MainFrame.titleFS:SetText(title)
|
|
MainFrame.pageFS:SetText("第 " .. pageNum .. " 页")
|
|
MainFrame.bodyFS:SetText(text)
|
|
|
|
MainFrame.bodyFS:ClearAllPoints()
|
|
MainFrame.bodyFS:SetPoint("TOPLEFT", MainFrame.scroll.content, "TOPLEFT", 2, -6)
|
|
MainFrame.scroll.content:SetHeight(TextHeight(MainFrame.bodyFS, 14) + 18)
|
|
MainFrame.scroll:SetVerticalScroll(0)
|
|
|
|
local hasPrevApi = ItemTextHasPrevPage and IsTrue(ItemTextHasPrevPage()) or false
|
|
local hasNextApi = ItemTextHasNextPage and IsTrue(ItemTextHasNextPage()) or false
|
|
local canPrev = (pageNum and pageNum > 1) or hasPrevApi
|
|
MainFrame.prevBtn:SetDisabled(not canPrev)
|
|
MainFrame.nextBtn:SetDisabled(not hasNextApi)
|
|
end
|
|
|
|
local function QueueRefresh(delay, count)
|
|
if not MainFrame then return end
|
|
MainFrame._refreshDelay = delay or 0.05
|
|
MainFrame._refreshCount = count or 1
|
|
end
|
|
|
|
local function CloseBookFrame()
|
|
if MainFrame and MainFrame:IsVisible() then
|
|
MainFrame:Hide()
|
|
end
|
|
if CloseItemText then
|
|
pcall(CloseItemText)
|
|
elseif ItemTextFrame and ItemTextFrame.Hide then
|
|
pcall(ItemTextFrame.Hide, ItemTextFrame)
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Initialize
|
|
--------------------------------------------------------------------------------
|
|
function BUI:Initialize()
|
|
if MainFrame then return end
|
|
|
|
MainFrame = CreateFrame("Frame", "SFramesBookFrame", 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)
|
|
|
|
local header = CreateFrame("Frame", nil, MainFrame)
|
|
header:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", 0, 0)
|
|
header:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", 0, 0)
|
|
header:SetHeight(HEADER_H)
|
|
|
|
local titleFS = header:CreateFontString(nil, "OVERLAY")
|
|
titleFS:SetFont(GetFont(), 14, "OUTLINE")
|
|
titleFS:SetPoint("LEFT", header, "LEFT", SIDE_PAD, 0)
|
|
titleFS:SetPoint("RIGHT", header, "RIGHT", -96, 0)
|
|
titleFS:SetJustifyH("LEFT")
|
|
titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
|
MainFrame.titleFS = titleFS
|
|
|
|
local pageFS = header:CreateFontString(nil, "OVERLAY")
|
|
pageFS:SetFont(GetFont(), 11, "OUTLINE")
|
|
pageFS:SetPoint("RIGHT", header, "RIGHT", -30, 0)
|
|
pageFS:SetJustifyH("RIGHT")
|
|
pageFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
MainFrame.pageFS = pageFS
|
|
|
|
local closeBtn = CreateFrame("Button", nil, MainFrame, "UIPanelCloseButton")
|
|
closeBtn:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", 2, 2)
|
|
closeBtn:SetWidth(24); closeBtn:SetHeight(24)
|
|
|
|
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])
|
|
|
|
local contentArea = CreateFrame("Frame", nil, MainFrame)
|
|
contentArea:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", SIDE_PAD, -(HEADER_H + 4))
|
|
contentArea:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, BOTTOM_H + 4)
|
|
MainFrame.contentArea = contentArea
|
|
|
|
local scroll = CreateScrollArea(contentArea, "SFramesBookScroll")
|
|
scroll:SetPoint("TOPLEFT", contentArea, "TOPLEFT", 0, 0)
|
|
scroll:SetPoint("BOTTOMRIGHT", contentArea, "BOTTOMRIGHT", 0, 0)
|
|
MainFrame.scroll = scroll
|
|
|
|
local bodyFS = scroll.content:CreateFontString(nil, "OVERLAY")
|
|
bodyFS:SetFont(GetFont(), BODY_FONT_SIZE)
|
|
if bodyFS.SetSpacing then
|
|
bodyFS:SetSpacing(BODY_LINE_SPACING)
|
|
end
|
|
bodyFS:SetWidth(CONTENT_W - 4)
|
|
bodyFS:SetJustifyH("LEFT")
|
|
bodyFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
|
|
MainFrame.bodyFS = bodyFS
|
|
|
|
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])
|
|
|
|
MainFrame.prevBtn = CreateActionBtn(MainFrame, "上一页", 90)
|
|
MainFrame.prevBtn:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", SIDE_PAD, 8)
|
|
MainFrame.prevBtn:SetScript("OnClick", function()
|
|
if this.disabled then return end
|
|
if ItemTextPrevPage then
|
|
ItemTextPrevPage()
|
|
QueueRefresh(0.05, 5)
|
|
end
|
|
end)
|
|
|
|
MainFrame.closeBtn = CreateActionBtn(MainFrame, "关闭", 90)
|
|
MainFrame.closeBtn:SetPoint("BOTTOM", MainFrame, "BOTTOM", 0, 8)
|
|
MainFrame.closeBtn:SetScript("OnClick", function()
|
|
CloseBookFrame()
|
|
end)
|
|
|
|
MainFrame.nextBtn = CreateActionBtn(MainFrame, "下一页", 90)
|
|
MainFrame.nextBtn:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, 8)
|
|
MainFrame.nextBtn:SetScript("OnClick", function()
|
|
if this.disabled then return end
|
|
if ItemTextNextPage then
|
|
ItemTextNextPage()
|
|
QueueRefresh(0.05, 5)
|
|
end
|
|
end)
|
|
|
|
MainFrame:SetScript("OnHide", function()
|
|
if ItemTextFrame and ItemTextFrame:IsVisible() then
|
|
pcall(ItemTextFrame.Hide, ItemTextFrame)
|
|
end
|
|
end)
|
|
MainFrame:SetScript("OnUpdate", function()
|
|
if not this._refreshCount or this._refreshCount <= 0 then return end
|
|
this._refreshDelay = (this._refreshDelay or 0) - (arg1 or 0)
|
|
if this._refreshDelay > 0 then return end
|
|
UpdateBookContent()
|
|
this._refreshCount = this._refreshCount - 1
|
|
if this._refreshCount > 0 then
|
|
this._refreshDelay = 0.08
|
|
end
|
|
end)
|
|
|
|
MainFrame:RegisterEvent("ITEM_TEXT_BEGIN")
|
|
MainFrame:RegisterEvent("ITEM_TEXT_READY")
|
|
MainFrame:RegisterEvent("ITEM_TEXT_CLOSED")
|
|
MainFrame:SetScript("OnEvent", function()
|
|
if event == "ITEM_TEXT_BEGIN" then
|
|
HideBlizzardItemText()
|
|
UpdateBookContent()
|
|
QueueRefresh(0.05, 5)
|
|
MainFrame:Show()
|
|
elseif event == "ITEM_TEXT_READY" then
|
|
HideBlizzardItemText()
|
|
UpdateBookContent()
|
|
QueueRefresh(0.05, 5)
|
|
if not MainFrame:IsVisible() then
|
|
MainFrame:Show()
|
|
end
|
|
elseif event == "ITEM_TEXT_CLOSED" then
|
|
MainFrame._refreshCount = 0
|
|
MainFrame:Hide()
|
|
end
|
|
end)
|
|
|
|
MainFrame:Hide()
|
|
tinsert(UISpecialFrames, "SFramesBookFrame")
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Bootstrap
|
|
--------------------------------------------------------------------------------
|
|
local bootstrap = CreateFrame("Frame")
|
|
bootstrap:RegisterEvent("PLAYER_LOGIN")
|
|
bootstrap:SetScript("OnEvent", function()
|
|
if event == "PLAYER_LOGIN" then
|
|
BUI:Initialize()
|
|
end
|
|
end)
|