init
This commit is contained in:
375
BookUI.lua
Normal file
375
BookUI.lua
Normal file
@@ -0,0 +1,375 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- 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 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
|
||||
-- Keep paragraph gap readable: collapse 3+ consecutive newlines.
|
||||
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)
|
||||
Reference in New Issue
Block a user