Files
Nanami-UI/Mail.lua
2026-04-09 09:46:47 +08:00

2247 lines
93 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--------------------------------------------------------------------------------
-- Nanami-UI: Mail UI (Mail.lua)
-- Replaces MailFrame with Nanami-UI styled interface
-- Tabs: Inbox / Send
-- Inbox: select-all / per-item checkboxes, batch collect
-- Send: multi-item queue, split into individual sends
--------------------------------------------------------------------------------
SFrames = SFrames or {}
SFrames.Mail = {}
local ML = SFrames.Mail
SFramesDB = SFramesDB or {}
if SFramesDB.mailContacts == nil then
SFramesDB.mailContacts = {}
end
-- Save original Blizzard functions BEFORE other addons (TurtleMail etc.) hook them.
-- File-level locals are captured at load time, which is before later-alphabetical addons.
local _ClickSendMailItemButton = ClickSendMailItemButton
local _PickupContainerItem = PickupContainerItem
local _ClearCursor = ClearCursor
local _SendMail = SendMail
--------------------------------------------------------------------------------
-- Theme
--------------------------------------------------------------------------------
local T = SFrames.Theme:Extend({
moneyGold = { 1, 0.84, 0.0 },
moneySilver = { 0.78, 0.78, 0.78 },
moneyCopper = { 0.71, 0.43, 0.18 },
codColor = { 1.0, 0.35, 0.35 },
unreadMark = { 1.0, 0.85, 0.3 },
successText = { 0.3, 1.0, 0.4 },
errorText = { 1.0, 0.3, 0.3 },
friendColor = { 0.3, 1.0, 0.5 },
guildColor = { 0.4, 0.8, 1.0 },
whoColor = { 0.85, 0.85, 0.85 },
})
--------------------------------------------------------------------------------
-- Shared state table (avoids excessive upvalues)
--------------------------------------------------------------------------------
local S = {
frame = nil, -- MainFrame
inboxRows = {},
currentTab = 1,
inboxPage = 1,
bagPage = 1,
inboxChecked = {},
collectQueue = {},
collectTimer = nil,
isCollecting = false,
sendQueue = {},
isSending = false,
collectElapsed = 0,
multiSend = nil, -- active multi-send state table
codMode = false, -- send panel: 付款取信 mode toggle
}
local L = {
W = 360, H = 480, HEADER = 34, PAD = 12, TAB_H = 28,
BOTTOM = 46, ROWS = 8, ROW_H = 38, ICON = 30, MAX_SEND = 12,
BAG_SLOT = 38, BAG_GAP = 3, BAG_PER_ROW = 8, BAG_ROWS = 8,
}
StaticPopupDialogs["NANAMI_MAIL_COD_CONFIRM"] = {
text = "确认支付 %s 取回此物品?",
button1 = "确认",
button2 = "取消",
OnAccept = function() end,
timeout = 0,
whileDead = true,
hideOnEscape = true,
}
--------------------------------------------------------------------------------
-- 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 FormatMoneyString(copper)
if not copper or copper <= 0 then return "0|cFFB87333c|r" end
local g = math.floor(copper / 10000)
local sv = math.floor(math.mod(copper, 10000) / 100)
local c = math.mod(copper, 100)
local parts = ""
if g > 0 then parts = parts .. "|cFFFFD700" .. g .. "g|r " end
if sv > 0 then parts = parts .. "|cFFC7C7CF" .. sv .. "s|r " end
if c > 0 then parts = parts .. "|cFFB87333" .. c .. "c|r" end
return parts
end
local function CreateMoneyIcons(parent, fontSize, iconSize)
fontSize = fontSize or 10
iconSize = iconSize or 11
local font = GetFont()
local mf = CreateFrame("Frame", nil, parent)
mf:SetWidth(100); mf:SetHeight(iconSize + 2)
local function MakePair(mfRef, r, g, b, tcL, tcR)
local txt = mfRef:CreateFontString(nil, "OVERLAY")
txt:SetFont(font, fontSize, "OUTLINE"); txt:SetTextColor(r, g, b)
local tex = mfRef:CreateTexture(nil, "ARTWORK")
tex:SetTexture("Interface\\MoneyFrame\\UI-MoneyIcons")
tex:SetTexCoord(tcL, tcR, 0, 1); tex:SetWidth(iconSize); tex:SetHeight(iconSize)
return txt, tex
end
mf.gTxt, mf.gTex = MakePair(mf, 1, 0.84, 0, 0, 0.25)
mf.sTxt, mf.sTex = MakePair(mf, 0.78, 0.78, 0.81, 0.25, 0.5)
mf.cTxt, mf.cTex = MakePair(mf, 0.72, 0.45, 0.2, 0.5, 0.75)
function mf:SetMoney(copper)
copper = copper or 0
local gv = math.floor(copper / 10000)
local sv = math.floor(math.mod(copper, 10000) / 100)
local cv = math.mod(copper, 100)
self.gTxt:Hide(); self.gTex:Hide(); self.gTxt:ClearAllPoints()
self.sTxt:Hide(); self.sTex:Hide(); self.sTxt:ClearAllPoints()
self.cTxt:Hide(); self.cTex:Hide(); self.cTxt:ClearAllPoints()
self.gTex:ClearAllPoints(); self.sTex:ClearAllPoints(); self.cTex:ClearAllPoints()
if copper <= 0 then return end
local anchor = nil
local function Attach(txt, tex, val)
txt:SetText(val)
txt:ClearAllPoints(); tex:ClearAllPoints()
if anchor then txt:SetPoint("LEFT", anchor, "RIGHT", 3, 0)
else txt:SetPoint("LEFT", self, "LEFT", 0, 0) end
tex:SetPoint("LEFT", txt, "RIGHT", 1, 0)
txt:Show(); tex:Show()
anchor = tex
end
if gv > 0 then Attach(self.gTxt, self.gTex, gv) end
if sv > 0 then Attach(self.sTxt, self.sTex, sv) end
if cv > 0 then Attach(self.cTxt, self.cTex, cv) end
end
return mf
end
local function FormatExpiry(daysLeft)
if not daysLeft then return "" end
local d = math.floor(daysLeft)
if d <= 0 then return "|cFFFF3333即将过期|r" end
if d == 1 then return "|cFFFF6633剩余 1 天|r" end
if d <= 3 then return "|cFFFFAA33剩余 " .. d .. " 天|r" end
return "|cFF88FF88" .. d .. " 天|r"
end
--------------------------------------------------------------------------------
-- Action Button Factory
--------------------------------------------------------------------------------
local function CreateActionBtn(parent, text, w)
local btn = CreateFrame("Button", nil, parent)
btn:SetWidth(w or 100)
btn:SetHeight(26)
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:SetText(text)
fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
btn.label = fs
btn.disabled = false
function btn:SetDisabled(flag)
self.disabled = flag
if flag then
self.label:SetTextColor(T.btnDisabledText[1], T.btnDisabledText[2], T.btnDisabledText[3])
self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], 0.5)
else
self.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
end
end
btn:SetScript("OnEnter", function()
if not this.disabled then
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
this.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3])
end
end)
btn:SetScript("OnLeave", function()
if not this.disabled then
this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
this.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
end
end)
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)
btn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
return btn
end
--------------------------------------------------------------------------------
-- Tab Button Factory
--------------------------------------------------------------------------------
local function CreateTabBtn(parent, text, w)
local btn = CreateFrame("Button", nil, parent)
btn:SetWidth(w or 70)
btn:SetHeight(22)
btn:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, tileSize = 0, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
btn:SetBackdropColor(T.tabBg[1], T.tabBg[2], T.tabBg[3], T.tabBg[4])
btn:SetBackdropBorderColor(T.tabBorder[1], T.tabBorder[2], T.tabBorder[3], 0.5)
local fs = btn:CreateFontString(nil, "OVERLAY")
fs:SetFont(GetFont(), 12, "OUTLINE")
fs:SetPoint("CENTER", 0, 0)
fs:SetText(text)
fs:SetTextColor(T.tabText[1], T.tabText[2], T.tabText[3])
btn.label = fs
btn:SetScript("OnEnter", function()
if not this.active 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])
end
end)
btn:SetScript("OnLeave", function()
if this.active then
this:SetBackdropColor(T.tabActiveBg[1], T.tabActiveBg[2], T.tabActiveBg[3], T.tabActiveBg[4])
this:SetBackdropBorderColor(T.tabActiveBorder[1], T.tabActiveBorder[2], T.tabActiveBorder[3], T.tabActiveBorder[4])
else
this:SetBackdropColor(T.tabBg[1], T.tabBg[2], T.tabBg[3], T.tabBg[4])
this:SetBackdropBorderColor(T.tabBorder[1], T.tabBorder[2], T.tabBorder[3], 0.5)
end
end)
function btn:SetActive(flag)
self.active = flag
if flag then
self:SetBackdropColor(T.tabActiveBg[1], T.tabActiveBg[2], T.tabActiveBg[3], T.tabActiveBg[4])
self:SetBackdropBorderColor(T.tabActiveBorder[1], T.tabActiveBorder[2], T.tabActiveBorder[3], T.tabActiveBorder[4])
self.label:SetTextColor(T.tabActiveText[1], T.tabActiveText[2], T.tabActiveText[3])
else
self:SetBackdropColor(T.tabBg[1], T.tabBg[2], T.tabBg[3], T.tabBg[4])
self:SetBackdropBorderColor(T.tabBorder[1], T.tabBorder[2], T.tabBorder[3], 0.5)
self.label:SetTextColor(T.tabText[1], T.tabText[2], T.tabText[3])
end
end
return btn
end
--------------------------------------------------------------------------------
-- Checkbox Factory
--------------------------------------------------------------------------------
local function CreateSmallCheckbox(parent, size)
local sz = size or 16
local cb = CreateFrame("Button", nil, parent)
cb:SetWidth(sz); cb:SetHeight(sz)
cb:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, tileSize = 0, edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
cb:SetBackdropColor(T.checkBg[1], T.checkBg[2], T.checkBg[3], T.checkBg[4])
cb:SetBackdropBorderColor(T.checkBorder[1], T.checkBorder[2], T.checkBorder[3], T.checkBorder[4])
local mark = cb:CreateTexture(nil, "OVERLAY")
mark:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
mark:SetAllPoints(cb); mark:Hide()
cb.mark = mark; cb.checked = false
function cb:SetChecked(flag)
self.checked = flag
if flag then self.mark:Show(); self:SetBackdropColor(T.checkFill[1], T.checkFill[2], T.checkFill[3], 0.3)
else self.mark:Hide(); self:SetBackdropColor(T.checkBg[1], T.checkBg[2], T.checkBg[3], T.checkBg[4]) end
end
function cb:IsChecked() return self.checked end
cb:SetScript("OnClick", function()
this:SetChecked(not this.checked)
if this.onToggle then this.onToggle(this.checked) end
end)
cb:SetScript("OnEnter", function() this:SetBackdropBorderColor(T.checkHoverBorder[1], T.checkHoverBorder[2], T.checkHoverBorder[3], T.checkHoverBorder[4]) end)
cb:SetScript("OnLeave", function() this:SetBackdropBorderColor(T.checkBorder[1], T.checkBorder[2], T.checkBorder[3], T.checkBorder[4]) end)
return cb
end
--------------------------------------------------------------------------------
-- Styled EditBox Factory
--------------------------------------------------------------------------------
local function CreateStyledEditBox(parent, width, height, numeric)
local eb = CreateFrame("EditBox", nil, parent)
eb:SetWidth(width or 120); eb:SetHeight(height or 22)
eb: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 },
})
eb:SetBackdropColor(T.inputBg[1], T.inputBg[2], T.inputBg[3], T.inputBg[4] or 0.95)
eb:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], 0.8)
eb:SetFont(GetFont(), 11, "OUTLINE")
eb:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
eb:SetJustifyH("LEFT"); eb:SetAutoFocus(false); eb:SetTextInsets(6, 6, 2, 2)
if numeric then eb:SetNumeric(true) end
return eb
end
--------------------------------------------------------------------------------
-- INBOX: Row Factory
--------------------------------------------------------------------------------
local function CreateInboxRow(parent, index)
local row = CreateFrame("Frame", "SFramesMailInboxRow" .. index, parent)
row:SetWidth(L.W - L.PAD * 2); row:SetHeight(L.ROW_H)
local bg = row:CreateTexture(nil, "BACKGROUND")
bg:SetTexture("Interface\\Buttons\\WHITE8X8"); bg:SetAllPoints(row)
local normalR, normalG, normalB, normalA
if math.mod(index, 2) == 0 then
normalR, normalG, normalB, normalA = T.rowNormal[1], T.rowNormal[2], T.rowNormal[3], T.rowNormal[4]
else
normalR, normalG, normalB, normalA = T.panelBg[1], T.panelBg[2], T.panelBg[3], 0.3
end
bg:SetVertexColor(normalR, normalG, normalB, normalA)
row.bg = bg
local hl = row:CreateTexture(nil, "ARTWORK")
hl:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
hl:SetBlendMode("ADD"); hl:SetAllPoints(row); hl:SetAlpha(0); hl:Hide()
row.highlight = hl
row:EnableMouse(false)
local cb = CreateSmallCheckbox(row, 16)
cb:SetPoint("LEFT", row, "LEFT", 4, 0)
cb:SetFrameLevel(row:GetFrameLevel() + 10)
row.checkbox = cb
local iconFrame = CreateFrame("Frame", nil, row)
iconFrame:SetWidth(L.ICON); iconFrame:SetHeight(L.ICON)
iconFrame:SetPoint("LEFT", cb, "RIGHT", 6, 0)
iconFrame:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 12, insets = { left = 2, right = 2, top = 2, bottom = 2 },
})
iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
iconFrame:EnableMouse(true)
iconFrame:SetScript("OnEnter", function()
if not row.mailIndex then return end
row.highlight:SetAlpha(0.1); row.highlight:Show()
GameTooltip:SetOwner(iconFrame, "ANCHOR_RIGHT")
pcall(GameTooltip.SetInboxItem, GameTooltip, row.mailIndex)
GameTooltip:Show()
end)
iconFrame:SetScript("OnLeave", function()
row.highlight:SetAlpha(0); row.highlight:Hide()
GameTooltip:Hide()
end)
iconFrame:SetScript("OnMouseUp", function()
if row.mailIndex then ML:ShowMailDetail(row.mailIndex) end
end)
row.iconFrame = iconFrame
local icon = iconFrame:CreateTexture(nil, "ARTWORK")
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
icon:SetPoint("TOPLEFT", 3, -3); icon:SetPoint("BOTTOMRIGHT", -3, 3)
row.icon = icon
local font = GetFont()
local senderFS = row:CreateFontString(nil, "OVERLAY")
senderFS:SetFont(font, 11, "OUTLINE")
senderFS:SetPoint("TOPLEFT", iconFrame, "TOPRIGHT", 6, -2)
senderFS:SetWidth(110); senderFS:SetJustifyH("LEFT")
senderFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
row.senderFS = senderFS
local subjectFS = row:CreateFontString(nil, "OVERLAY")
subjectFS:SetFont(font, 9, "OUTLINE")
subjectFS:SetPoint("TOPLEFT", senderFS, "BOTTOMLEFT", 0, -1)
subjectFS:SetWidth(110); subjectFS:SetJustifyH("LEFT")
subjectFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
row.subjectFS = subjectFS
local moneyFrame = CreateMoneyIcons(row, 9, 10)
moneyFrame:SetPoint("RIGHT", row, "RIGHT", -52, 6)
row.moneyFrame = moneyFrame
local codFS = row:CreateFontString(nil, "OVERLAY")
codFS:SetFont(font, 9, "OUTLINE"); codFS:SetTextColor(1, 0.33, 0.33)
codFS:SetPoint("RIGHT", moneyFrame, "LEFT", -2, 0)
codFS:Hide()
row.codFS = codFS
local expiryFS = row:CreateFontString(nil, "OVERLAY")
expiryFS:SetFont(font, 9, "OUTLINE")
expiryFS:SetPoint("RIGHT", row, "RIGHT", -52, -6); expiryFS:SetJustifyH("RIGHT")
row.expiryFS = expiryFS
local takeBtn = CreateActionBtn(row, "收取", 40)
takeBtn:SetHeight(17); takeBtn:SetPoint("TOPRIGHT", row, "TOPRIGHT", -6, -3)
takeBtn:SetFrameLevel(row:GetFrameLevel() + 10)
row.takeBtn = takeBtn
local returnBtn = CreateActionBtn(row, "退回", 40)
returnBtn:SetHeight(17); returnBtn:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -6, 3)
returnBtn:SetFrameLevel(row:GetFrameLevel() + 10)
row.returnBtn = returnBtn
row.mailIndex = nil
return row
end
--------------------------------------------------------------------------------
-- INBOX: Update
--------------------------------------------------------------------------------
local function UpdateInbox()
if not S.frame or not S.frame:IsVisible() or S.currentTab ~= 1 then return end
local numItems = GetInboxNumItems()
local totalPages = math.max(1, math.ceil(numItems / L.ROWS))
if S.inboxPage > totalPages then S.inboxPage = totalPages end
if S.inboxPage < 1 then S.inboxPage = 1 end
S.frame.inboxPageText:SetText(string.format("第 %d / %d 页 (%d 封)", S.inboxPage, totalPages, numItems))
S.frame.inboxPrevBtn:SetDisabled(S.inboxPage <= 1)
S.frame.inboxNextBtn:SetDisabled(S.inboxPage >= totalPages)
for i = 1, L.ROWS do
local row = S.inboxRows[i]
local mi = (S.inboxPage - 1) * L.ROWS + i
if mi <= numItems then
local _, _, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead = GetInboxHeaderInfo(mi)
row.mailIndex = mi
if hasItem then
local _, itemTex = GetInboxItem(mi)
row.icon:SetTexture(itemTex or "Interface\\Icons\\INV_Misc_Note_01")
elseif money and money > 0 then
row.icon:SetTexture("Interface\\Icons\\INV_Misc_Coin_01")
else
row.icon:SetTexture("Interface\\Icons\\INV_Misc_Note_01")
end
row.senderFS:SetText(sender or "未知")
if not wasRead then
row.senderFS:SetTextColor(T.unreadMark[1], T.unreadMark[2], T.unreadMark[3])
else
row.senderFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
end
row.subjectFS:SetText(subject or "")
row.codFS:Hide(); row.codFS:SetText("")
if money and money > 0 then
row.moneyFrame:SetMoney(money)
elseif CODAmount and CODAmount > 0 then
row.codFS:SetText("付款:"); row.codFS:Show()
row.moneyFrame:SetMoney(CODAmount)
else
row.moneyFrame:SetMoney(0)
end
row.expiryFS:SetText(FormatExpiry(daysLeft))
local canTake = hasItem or (money and money > 0)
row.takeBtn:SetDisabled(not canTake)
row.takeBtn:SetScript("OnClick", function()
if row.mailIndex then
if hasItem then
if CODAmount and CODAmount > 0 then
local codStr = FormatMoneyString(CODAmount)
local idx = row.mailIndex
StaticPopupDialogs["NANAMI_MAIL_COD_CONFIRM"].OnAccept = function()
TakeInboxItem(idx)
end
StaticPopup_Show("NANAMI_MAIL_COD_CONFIRM", codStr)
else
TakeInboxItem(row.mailIndex)
end
elseif money and money > 0 then
local idx = row.mailIndex
TakeInboxMoney(idx)
if not S.deleteTimer then S.deleteTimer = CreateFrame("Frame") end
S.deleteElapsed = 0
S.deleteTimer:SetScript("OnUpdate", function()
S.deleteElapsed = S.deleteElapsed + arg1
if S.deleteElapsed >= 0.5 then
this:SetScript("OnUpdate", nil)
if idx <= GetInboxNumItems() then DeleteInboxItem(idx) end
end
end)
end
end
end)
row.returnBtn:SetScript("OnClick", function()
if row.mailIndex then ReturnInboxItem(row.mailIndex) end
end)
row.checkbox:SetChecked(S.inboxChecked[mi] == true)
row.checkbox.onToggle = function(checked)
S.inboxChecked[mi] = checked or nil
local any = false
for _, v in pairs(S.inboxChecked) do if v then any = true; break end end
S.frame.collectSelectedBtn:SetDisabled(not any)
end
if CODAmount and CODAmount > 0 then
row.checkbox:SetChecked(false); row.checkbox.disabled = true; S.inboxChecked[mi] = nil
else
row.checkbox.disabled = false
end
row:Show()
else
row.mailIndex = nil; row:Hide()
end
end
local hasChecked = false
for _, v in pairs(S.inboxChecked) do if v then hasChecked = true; break end end
S.frame.collectSelectedBtn:SetDisabled(not hasChecked and not S.isCollecting)
S.frame.collectAllBtn:SetDisabled(numItems == 0 and not S.isCollecting)
if S.isCollecting then
S.frame.collectAllBtn.label:SetText("收取中...")
S.frame.collectSelectedBtn.label:SetText("收取中...")
else
S.frame.collectAllBtn.label:SetText("全部收取")
S.frame.collectSelectedBtn.label:SetText("收取选中")
end
end
--------------------------------------------------------------------------------
-- INBOX: Batch Collect
--------------------------------------------------------------------------------
local function StopCollecting()
S.isCollecting = false; S.collectQueue = {}; S.collectPendingDelete = nil
if S.collectTimer then S.collectTimer:SetScript("OnUpdate", nil) end
if S.currentTab == 3 then ML:UpdateMailBag() else UpdateInbox() end
end
local function ProcessCollectQueue()
if S.collectPendingDelete then
local mi = S.collectPendingDelete
S.collectPendingDelete = nil
if mi <= GetInboxNumItems() then DeleteInboxItem(mi) end
return
end
if table.getn(S.collectQueue) == 0 then StopCollecting(); return end
local mi = table.remove(S.collectQueue, 1)
if mi > GetInboxNumItems() then ProcessCollectQueue(); return end
local _, _, _, _, money, CODAmount, _, hasItem = GetInboxHeaderInfo(mi)
if CODAmount and CODAmount > 0 then ProcessCollectQueue(); return end
if hasItem then TakeInboxItem(mi)
elseif money and money > 0 then
TakeInboxMoney(mi)
S.collectPendingDelete = mi
else DeleteInboxItem(mi) end
end
local function StartCollecting(indices)
if S.isCollecting then return end
S.collectQueue = {}
for i = table.getn(indices), 1, -1 do table.insert(S.collectQueue, indices[i]) end
if table.getn(S.collectQueue) == 0 then return end
S.isCollecting = true; S.collectElapsed = 0
if not S.collectTimer then S.collectTimer = CreateFrame("Frame") end
local nextDelay = 0
S.collectTimer:SetScript("OnUpdate", function()
if not S.isCollecting then this:SetScript("OnUpdate", nil); return end
S.collectElapsed = S.collectElapsed + arg1
if S.collectElapsed >= nextDelay then
S.collectElapsed = 0; nextDelay = 0.5; ProcessCollectQueue()
end
end)
UpdateInbox()
end
local function CollectAll()
local n = GetInboxNumItems()
if n == 0 then return end
local idx = {}
for i = 1, n do
local _, _, _, _, _, COD = GetInboxHeaderInfo(i)
if not COD or COD == 0 then table.insert(idx, i) end
end
StartCollecting(idx)
end
local function CollectSelected()
local idx = {}
for k, v in pairs(S.inboxChecked) do if v then table.insert(idx, k) end end
table.sort(idx)
if table.getn(idx) == 0 then return end
S.inboxChecked = {}
StartCollecting(idx)
end
local function SelectAllInbox()
local n = GetInboxNumItems()
for i = 1, n do
local _, _, _, _, _, COD = GetInboxHeaderInfo(i)
if not COD or COD == 0 then S.inboxChecked[i] = true end
end
UpdateInbox()
end
local function DeselectAllInbox()
S.inboxChecked = {}; UpdateInbox()
end
--------------------------------------------------------------------------------
-- SEND: Item Queue
--------------------------------------------------------------------------------
local function AddSendItem(bag, slot)
if table.getn(S.sendQueue) >= L.MAX_SEND then
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 发送队列已满 (" .. L.MAX_SEND .. " 件)")
return
end
local link = GetContainerItemLink(bag, slot)
if not link then return end
local texture, count = GetContainerItemInfo(bag, slot)
for i = 1, table.getn(S.sendQueue) do
if S.sendQueue[i].bag == bag and S.sendQueue[i].slot == slot then
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 该物品已在发送列表中"); return
end
end
table.insert(S.sendQueue, { bag = bag, slot = slot, link = link, texture = texture, count = count or 1 })
end
--------------------------------------------------------------------------------
-- SEND: Accept cursor drag (find the locked bag slot the cursor picked up)
--------------------------------------------------------------------------------
local function AcceptCursorItem()
if not CursorHasItem() then return false end
for bag = 0, 4 do
local slots = GetContainerNumSlots(bag)
for slot = 1, slots do
local _, _, locked = GetContainerItemInfo(bag, slot)
if locked then
AddSendItem(bag, slot)
ClearCursor()
ML:UpdateSendPanel()
return true
end
end
end
ClearCursor()
return false
end
local function RemoveSendItem(index)
if index >= 1 and index <= table.getn(S.sendQueue) then table.remove(S.sendQueue, index) end
end
local function ClearSendItems() S.sendQueue = {} end
local function FlashStatus(text, color, duration)
if not S.frame or not S.frame.sendStatus then return end
S.frame.sendStatus:SetText(text)
S.frame.sendStatus:SetTextColor(color[1], color[2], color[3])
S.statusFadeElapsed = 0
if not S.statusFadeTimer then S.statusFadeTimer = CreateFrame("Frame") end
S.statusFadeTimer:SetScript("OnUpdate", function()
S.statusFadeElapsed = S.statusFadeElapsed + arg1
if S.statusFadeElapsed >= (duration or 3) then
S.frame.sendStatus:SetText("")
this:SetScript("OnUpdate", nil)
end
end)
end
--------------------------------------------------------------------------------
-- SEND: Multi-Send Logic
--------------------------------------------------------------------------------
local function ResetSendForm()
if not S.frame then return end
ClearSendItems()
S.codMode = false
if S.frame.UpdateMoneyToggle then S.frame.UpdateMoneyToggle() end
if S.frame.toEditBox then S.frame.toEditBox:SetText("") end
if S.frame.subjectEditBox then S.frame.subjectEditBox:SetText("") end
if S.frame.bodyEditBox then S.frame.bodyEditBox:SetText("") end
if S.frame.goldEB then S.frame.goldEB:SetText("0") end
if S.frame.silverEB then S.frame.silverEB:SetText("0") end
if S.frame.copperEB then S.frame.copperEB:SetText("0") end
ML:UpdateSendPanel()
end
local function FinishMultiSend()
local ms = S.multiSend
S.multiSend = nil
S.isSending = false
if S.updateFrame then S.updateFrame:SetScript("OnUpdate", nil) end
ResetSendForm()
if S.frame and S.frame.sendBtn then
S.frame.sendBtn.label:SetText("发送"); S.frame.sendBtn:SetDisabled(false)
end
if ms then
DEFAULT_CHAT_FRAME:AddMessage("|cFF88FF88[Nanami-Mail]|r 所有 " .. ms.total .. " 封邮件已发送完成")
FlashStatus("全部发送完成!", T.successText, 3)
end
end
local function AbortMultiSend(reason)
S.multiSend = nil
S.isSending = false
if S.updateFrame then S.updateFrame:SetScript("OnUpdate", nil) end
if S.frame and S.frame.sendBtn then
S.frame.sendBtn.label:SetText("发送"); S.frame.sendBtn:SetDisabled(false)
end
if S.frame and S.frame.sendStatus then S.frame.sendStatus:SetText("") end
if reason then
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r " .. reason)
FlashStatus("发送失败!", T.errorText, 5)
end
end
local function DoMultiSend(recipient, subject, body, money)
if S.isSending then return end
if not recipient or recipient == "" then
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 请输入收件人名字"); return
end
if not subject or subject == "" then subject = "Mail" end
if S.frame and S.frame.sendBtn then
S.frame.sendBtn.label:SetText("发送中..."); S.frame.sendBtn:SetDisabled(true)
end
local items = {}
for i = 1, table.getn(S.sendQueue) do table.insert(items, S.sendQueue[i]) end
-- No attachments: plain text / money mail (付款取信 requires attachments)
if table.getn(items) == 0 then
if S.codMode and money and money > 0 then
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 付款取信模式需要添加附件物品")
if S.frame and S.frame.sendBtn then
S.frame.sendBtn.label:SetText("发送"); S.frame.sendBtn:SetDisabled(false)
end
return
end
if money and money > 0 then SetSendMailMoney(money) end
SendMail(recipient, subject, body or "")
return
end
S.multiSend = {
items = items,
recipient = recipient,
subject = subject or "",
body = body or "",
money = money,
codMode = S.codMode,
total = table.getn(items),
sentCount = 0,
phase = "attach", -- "attach" → "wait_send" → "cooldown" → "attach" ...
elapsed = 0,
}
S.isSending = true
if not S.updateFrame then S.updateFrame = CreateFrame("Frame") end
S.updateFrame:SetScript("OnUpdate", function()
local ms = S.multiSend
if not ms then this:SetScript("OnUpdate", nil); return end
ms.elapsed = ms.elapsed + arg1
---------------------------------------------------------------
-- Phase: attach — pick next item, attach to Blizzard mail slot, then send
---------------------------------------------------------------
if ms.phase == "attach" then
ms.sentCount = ms.sentCount + 1
if ms.sentCount > ms.total then
FinishMultiSend()
return
end
local item = ms.items[ms.sentCount]
if not item then FinishMultiSend(); return end
if S.frame and S.frame.sendStatus then
if S.statusFadeTimer then S.statusFadeTimer:SetScript("OnUpdate", nil) end
S.frame.sendStatus:SetText("发送中 " .. ms.sentCount .. "/" .. ms.total)
S.frame.sendStatus:SetTextColor(T.successText[1], T.successText[2], T.successText[3])
end
-- Ensure Blizzard send tab is active
if MailFrameTab_OnClick then MailFrameTab_OnClick(2) end
-- Check item still in bag
if not GetContainerItemLink(item.bag, item.slot) then
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 第" .. ms.sentCount .. "件物品已不在背包,跳过")
ms.elapsed = 0
return
end
-- Attach: clear → pick up → place
_ClearCursor()
_ClickSendMailItemButton()
_ClearCursor()
_PickupContainerItem(item.bag, item.slot)
_ClickSendMailItemButton()
-- Verify
if not GetSendMailItem() then
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 第" .. ms.sentCount .. "件附件挂载失败,跳过")
_ClearCursor()
ms.elapsed = 0
return
end
-- Money or 付款取信
if ms.money and ms.money > 0 then
if ms.codMode then
SetSendMailCOD(ms.money)
elseif ms.sentCount == 1 then
SetSendMailMoney(ms.money)
end
end
-- Send this single-attachment mail
ms.sendOk = false
ms.phase = "wait_send"
ms.elapsed = 0
SendMail(ms.recipient, ms.subject, ms.body)
---------------------------------------------------------------
-- Phase: wait_send — waiting for MAIL_SEND_SUCCESS
---------------------------------------------------------------
elseif ms.phase == "wait_send" then
if ms.sendOk then
ms.phase = "cooldown"
ms.elapsed = 0
elseif ms.elapsed >= 15 then
AbortMultiSend("发送超时,已停止")
end
---------------------------------------------------------------
-- Phase: cooldown — let server & Blizzard UI fully reset
---------------------------------------------------------------
elseif ms.phase == "cooldown" then
if ms.elapsed >= 0.6 then
ms.phase = "attach"
ms.elapsed = 0
end
end
end)
end
--------------------------------------------------------------------------------
-- SEND: Panel Update
--------------------------------------------------------------------------------
function ML:UpdateSendPanel()
if not S.frame or S.currentTab ~= 2 then return end
if not S.frame.sendItemSlots then return end
for i = 1, L.MAX_SEND do
local slot = S.frame.sendItemSlots[i]
if not slot then break end
local entry = S.sendQueue[i]
if entry then
slot.icon:SetTexture(entry.texture); slot.icon:Show()
slot.countFS:SetText(entry.count > 1 and entry.count or "")
slot.removeBtn:Show(); slot.hasItem = true
else
slot.icon:SetTexture(nil); slot.icon:Hide()
slot.countFS:SetText(""); slot.removeBtn:Hide(); slot.hasItem = false
end
end
end
--------------------------------------------------------------------------------
-- BUILD: Main frame + header + tabs
--------------------------------------------------------------------------------
local function BuildMainFrame()
local f = CreateFrame("Frame", "SFramesMailFrame", UIParent)
f:SetWidth(L.W); f:SetHeight(L.H)
f:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 16, -104)
f:SetFrameStrata("HIGH"); f:SetToplevel(true)
f:EnableMouse(true); f:SetMovable(true); f:RegisterForDrag("LeftButton")
f:SetScript("OnDragStart", function() this:StartMoving() end)
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
SetRoundBackdrop(f); CreateShadow(f)
S.frame = f
local font = GetFont()
local header = CreateFrame("Frame", nil, f)
header:SetPoint("TOPLEFT", 0, 0); header:SetPoint("TOPRIGHT", 0, 0); header:SetHeight(L.HEADER)
header:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
header:SetBackdropColor(T.headerBg[1], T.headerBg[2], T.headerBg[3], T.headerBg[4])
local titleIco = SFrames:CreateIcon(header, "mail", 16)
titleIco:SetDrawLayer("OVERLAY")
titleIco:SetPoint("LEFT", header, "LEFT", L.PAD, 0)
titleIco:SetVertexColor(T.gold[1], T.gold[2], T.gold[3])
local titleFS = header:CreateFontString(nil, "OVERLAY")
titleFS:SetFont(font, 14, "OUTLINE"); titleFS:SetPoint("LEFT", titleIco, "RIGHT", 5, 0)
titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3]); titleFS:SetText("邮箱")
local closeBtn = CreateFrame("Button", nil, header)
closeBtn:SetWidth(20); closeBtn:SetHeight(20); closeBtn:SetPoint("RIGHT", header, "RIGHT", -8, 0)
local closeTex = closeBtn:CreateTexture(nil, "ARTWORK")
closeTex:SetTexture("Interface\\AddOns\\Nanami-UI\\img\\icon")
closeTex:SetTexCoord(0.25, 0.375, 0, 0.125); closeTex:SetAllPoints()
closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3])
closeBtn:SetScript("OnClick", function() S.frame:Hide() end)
closeBtn:SetScript("OnEnter", function() closeTex:SetVertexColor(1, 0.6, 0.7) end)
closeBtn:SetScript("OnLeave", function() closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3]) end)
local sep = f:CreateTexture(nil, "ARTWORK")
sep:SetTexture("Interface\\Buttons\\WHITE8X8"); sep:SetHeight(1)
sep:SetPoint("TOPLEFT", f, "TOPLEFT", 6, -L.HEADER)
sep:SetPoint("TOPRIGHT", f, "TOPRIGHT", -6, -L.HEADER)
sep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
local tabInbox = CreateTabBtn(f, "收件箱", 62)
tabInbox:SetPoint("TOPLEFT", f, "TOPLEFT", L.PAD, -(L.HEADER + 6))
tabInbox:SetScript("OnClick", function() S.currentTab = 1; ML:ShowInboxPanel() end)
f.tabInbox = tabInbox
local tabBag = CreateTabBtn(f, "邮包", 50)
tabBag:SetPoint("LEFT", tabInbox, "RIGHT", 4, 0)
tabBag:SetScript("OnClick", function() S.currentTab = 3; ML:ShowMailBagPanel() end)
f.tabBag = tabBag
local tabSend = CreateTabBtn(f, "发送", 62)
tabSend:SetPoint("LEFT", tabBag, "RIGHT", 4, 0)
tabSend:SetScript("OnClick", function() S.currentTab = 2; ML:ShowSendPanel() end)
f.tabSend = tabSend
f:Hide()
end
--------------------------------------------------------------------------------
-- BUILD: Inbox panel
--------------------------------------------------------------------------------
local function BuildInboxPanel()
local f = S.frame
local panelTop = L.HEADER + 6 + L.TAB_H + 4
local ip = CreateFrame("Frame", nil, f)
ip:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -panelTop)
ip:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
f.inboxPanel = ip
local font = GetFont()
local selAll = CreateActionBtn(ip, "全选", 50)
selAll:SetHeight(20); selAll:SetPoint("TOPLEFT", ip, "TOPLEFT", L.PAD, 0)
selAll:SetScript("OnClick", function() SelectAllInbox() end)
local desel = CreateActionBtn(ip, "取消全选", 66)
desel:SetHeight(20); desel:SetPoint("LEFT", selAll, "RIGHT", 4, 0)
desel:SetScript("OnClick", function() DeselectAllInbox() end)
local pageFS = ip:CreateFontString(nil, "OVERLAY")
pageFS:SetFont(font, 10, "OUTLINE"); pageFS:SetPoint("LEFT", desel, "RIGHT", 12, 0)
pageFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
f.inboxPageText = pageFS
for i = 1, L.ROWS do
local row = CreateInboxRow(ip, i)
row:SetPoint("TOPLEFT", ip, "TOPLEFT", L.PAD, -(26 + (i - 1) * L.ROW_H))
S.inboxRows[i] = row
end
local bsep = ip:CreateTexture(nil, "ARTWORK")
bsep:SetTexture("Interface\\Buttons\\WHITE8X8"); bsep:SetHeight(1)
bsep:SetPoint("BOTTOMLEFT", ip, "BOTTOMLEFT", 6, L.BOTTOM)
bsep:SetPoint("BOTTOMRIGHT", ip, "BOTTOMRIGHT", -6, L.BOTTOM)
bsep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
local prev = CreateActionBtn(ip, "<", 28)
prev:SetHeight(22); prev:SetPoint("BOTTOMLEFT", ip, "BOTTOMLEFT", L.PAD, 12)
prev:SetScript("OnClick", function() S.inboxPage = S.inboxPage - 1; UpdateInbox() end)
f.inboxPrevBtn = prev
local nxt = CreateActionBtn(ip, ">", 28)
nxt:SetHeight(22); nxt:SetPoint("LEFT", prev, "RIGHT", 4, 0)
nxt:SetScript("OnClick", function() S.inboxPage = S.inboxPage + 1; UpdateInbox() end)
f.inboxNextBtn = nxt
local colSel = CreateActionBtn(ip, "收取选中", 80)
colSel:SetHeight(24); colSel:SetPoint("BOTTOMRIGHT", ip, "BOTTOMRIGHT", -L.PAD, 10)
colSel:SetScript("OnClick", function()
if S.isCollecting then StopCollecting() else CollectSelected() end
end)
f.collectSelectedBtn = colSel
local colAll = CreateActionBtn(ip, "全部收取", 80)
colAll:SetHeight(24); colAll:SetPoint("RIGHT", colSel, "LEFT", -6, 0)
colAll:SetScript("OnClick", function()
if S.isCollecting then StopCollecting() else CollectAll() end
end)
f.collectAllBtn = colAll
end
--------------------------------------------------------------------------------
-- BUILD: Mail detail panel (overlays inbox when a mail is clicked)
--------------------------------------------------------------------------------
local function BuildDetailPanel()
local f = S.frame
local panelTop = L.HEADER + 6 + L.TAB_H + 4
local dp = CreateFrame("Frame", nil, f)
dp:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -panelTop)
dp:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
dp:Hide()
f.detailPanel = dp
local font = GetFont()
local pad = L.PAD
local backBtn = CreateActionBtn(dp, "< 返回", 60)
backBtn:SetHeight(20); backBtn:SetPoint("TOPLEFT", dp, "TOPLEFT", pad, 0)
backBtn:SetScript("OnClick", function() ML:HideMailDetail() end)
local senderFS = dp:CreateFontString(nil, "OVERLAY")
senderFS:SetFont(font, 11, "OUTLINE"); senderFS:SetJustifyH("LEFT")
senderFS:SetPoint("TOPLEFT", dp, "TOPLEFT", pad, -26)
senderFS:SetWidth(L.W - pad * 2)
senderFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
dp.senderFS = senderFS
local subjectFS = dp:CreateFontString(nil, "OVERLAY")
subjectFS:SetFont(font, 11, "OUTLINE"); subjectFS:SetJustifyH("LEFT")
subjectFS:SetPoint("TOPLEFT", senderFS, "BOTTOMLEFT", 0, -4)
subjectFS:SetWidth(L.W - pad * 2)
subjectFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
dp.subjectFS = subjectFS
local sep1 = dp:CreateTexture(nil, "ARTWORK")
sep1:SetTexture("Interface\\Buttons\\WHITE8X8"); sep1:SetHeight(1)
sep1:SetPoint("TOPLEFT", subjectFS, "BOTTOMLEFT", 0, -6)
sep1:SetPoint("RIGHT", dp, "RIGHT", -pad, 0)
sep1:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
-- Body scroll frame
local bsf = CreateFrame("ScrollFrame", "SFramesMailDetailScroll", dp, "UIPanelScrollFrameTemplate")
bsf:SetPoint("TOPLEFT", sep1, "BOTTOMLEFT", 0, -4)
bsf:SetWidth(L.W - pad * 2 - 24); bsf:SetHeight(180)
local bodyFS = CreateFrame("Frame", nil, bsf)
bodyFS:SetWidth(L.W - pad * 2 - 36); bodyFS:SetHeight(400)
local bodyText = bodyFS:CreateFontString(nil, "OVERLAY")
bodyText:SetFont(font, 11, "OUTLINE")
bodyText:SetPoint("TOPLEFT", 0, 0)
bodyText:SetWidth(L.W - pad * 2 - 36); bodyText:SetJustifyH("LEFT"); bodyText:SetJustifyV("TOP")
bodyText:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
bsf:SetScrollChild(bodyFS)
dp.bodyText = bodyText
dp.bodyFrame = bodyFS
local sep2 = dp:CreateTexture(nil, "ARTWORK")
sep2:SetTexture("Interface\\Buttons\\WHITE8X8"); sep2:SetHeight(1)
sep2:SetPoint("TOPLEFT", bsf, "BOTTOMLEFT", 0, -4)
sep2:SetPoint("RIGHT", dp, "RIGHT", -pad, 0)
sep2:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
-- Item + money info line
local infoFS = dp:CreateFontString(nil, "OVERLAY")
infoFS:SetFont(font, 10, "OUTLINE"); infoFS:SetJustifyH("LEFT")
infoFS:SetPoint("TOPLEFT", sep2, "BOTTOMLEFT", 0, -6)
infoFS:SetWidth(L.W - pad * 2)
infoFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
dp.infoFS = infoFS
local moneyLabel = dp:CreateFontString(nil, "OVERLAY")
moneyLabel:SetFont(font, 10, "OUTLINE"); moneyLabel:SetTextColor(1, 0.84, 0)
moneyLabel:SetPoint("TOPLEFT", infoFS, "BOTTOMLEFT", 0, -3)
moneyLabel:Hide()
dp.detailMoneyLabel = moneyLabel
local detailMoney = CreateMoneyIcons(dp, 10, 11)
detailMoney:SetPoint("LEFT", moneyLabel, "RIGHT", 4, 0)
detailMoney:Hide()
dp.detailMoney = detailMoney
local codLabel = dp:CreateFontString(nil, "OVERLAY")
codLabel:SetFont(font, 10, "OUTLINE"); codLabel:SetTextColor(1, 0.33, 0.33)
codLabel:SetPoint("LEFT", detailMoney, "RIGHT", 8, 0)
codLabel:Hide()
dp.detailCodLabel = codLabel
local detailCod = CreateMoneyIcons(dp, 10, 11)
detailCod:SetPoint("LEFT", codLabel, "RIGHT", 4, 0)
detailCod:Hide()
dp.detailCod = detailCod
-- Action buttons at bottom
local takeItemBtn = CreateActionBtn(dp, "收取物品", 72)
takeItemBtn:SetHeight(24); takeItemBtn:SetPoint("BOTTOMLEFT", dp, "BOTTOMLEFT", pad, 10)
dp.takeItemBtn = takeItemBtn
local takeMoneyBtn = CreateActionBtn(dp, "收取金币", 72)
takeMoneyBtn:SetHeight(24); takeMoneyBtn:SetPoint("LEFT", takeItemBtn, "RIGHT", 4, 0)
dp.takeMoneyBtn = takeMoneyBtn
local returnBtn = CreateActionBtn(dp, "退回", 50)
returnBtn:SetHeight(24); returnBtn:SetPoint("LEFT", takeMoneyBtn, "RIGHT", 4, 0)
dp.returnBtn = returnBtn
local deleteBtn = CreateActionBtn(dp, "删除", 50)
deleteBtn:SetHeight(24); deleteBtn:SetPoint("LEFT", returnBtn, "RIGHT", 4, 0)
dp.deleteBtn = deleteBtn
local replyBtn = CreateActionBtn(dp, "回复", 50)
replyBtn:SetHeight(24); replyBtn:SetPoint("BOTTOMRIGHT", dp, "BOTTOMRIGHT", -pad, 10)
dp.replyBtn = replyBtn
end
--------------------------------------------------------------------------------
-- Show / Hide mail detail
--------------------------------------------------------------------------------
function ML:ShowMailDetail(mailIndex)
if not S.frame or not S.frame.detailPanel then return end
local dp = S.frame.detailPanel
S.detailMailIndex = mailIndex
local _, _, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead = GetInboxHeaderInfo(mailIndex)
local bodyText = GetInboxText(mailIndex)
dp.senderFS:SetText("发件人: |cFFFFFFFF" .. (sender or "未知") .. "|r " .. FormatExpiry(daysLeft))
dp.subjectFS:SetText("主题: |cFFFFFFFF" .. (subject or "(无主题)") .. "|r")
dp.bodyText:SetText(bodyText or "(无正文)")
local ok, textH = pcall(function() return dp.bodyText:GetStringHeight() end)
if not ok or not textH then textH = 40 end
dp.bodyFrame:SetHeight(math.max(textH + 10, 40))
local info = ""
if hasItem then
local itemName, itemTex = GetInboxItem(mailIndex)
if itemName then info = info .. "|cFFFFD700附件:|r " .. itemName end
end
if info == "" and (not money or money <= 0) and (not CODAmount or CODAmount <= 0) then
info = "无附件,无金币"
end
dp.infoFS:SetText(info)
dp.detailMoneyLabel:Hide(); dp.detailMoney:Hide()
dp.detailCodLabel:Hide(); dp.detailCod:Hide()
if money and money > 0 then
dp.detailMoneyLabel:SetText("金额:"); dp.detailMoneyLabel:Show()
dp.detailMoney:SetMoney(money); dp.detailMoney:Show()
end
if CODAmount and CODAmount > 0 then
dp.detailCodLabel:SetText("付款取信:"); dp.detailCodLabel:Show()
dp.detailCod:SetMoney(CODAmount); dp.detailCod:Show()
end
-- Take items button
local canTakeItem = hasItem
dp.takeItemBtn:SetDisabled(not canTakeItem)
dp.takeItemBtn:SetScript("OnClick", function()
if S.detailMailIndex then
if CODAmount and CODAmount > 0 then
local codStr = FormatMoneyString(CODAmount)
local idx = S.detailMailIndex
StaticPopupDialogs["NANAMI_MAIL_COD_CONFIRM"].OnAccept = function()
TakeInboxItem(idx)
end
StaticPopup_Show("NANAMI_MAIL_COD_CONFIRM", codStr)
else
TakeInboxItem(S.detailMailIndex)
end
end
end)
-- Take money button
local canTakeMoney = money and money > 0 and (not CODAmount or CODAmount == 0)
dp.takeMoneyBtn:SetDisabled(not canTakeMoney)
dp.takeMoneyBtn:SetScript("OnClick", function()
if S.detailMailIndex then
local idx = S.detailMailIndex
local _, _, _, _, _, _, _, hi = GetInboxHeaderInfo(idx)
TakeInboxMoney(idx)
if not hi then
if not S.deleteTimer then S.deleteTimer = CreateFrame("Frame") end
S.deleteElapsed = 0
S.deleteTimer:SetScript("OnUpdate", function()
S.deleteElapsed = S.deleteElapsed + arg1
if S.deleteElapsed >= 0.5 then
this:SetScript("OnUpdate", nil)
if idx <= GetInboxNumItems() then DeleteInboxItem(idx) end
end
end)
end
ML:HideMailDetail()
end
end)
-- Return button
dp.returnBtn:SetScript("OnClick", function()
if S.detailMailIndex then
ReturnInboxItem(S.detailMailIndex)
ML:HideMailDetail()
end
end)
-- Delete button
dp.deleteBtn:SetScript("OnClick", function()
if S.detailMailIndex then
DeleteInboxItem(S.detailMailIndex)
ML:HideMailDetail()
end
end)
-- Reply button
dp.replyBtn:SetScript("OnClick", function()
if sender then
S.currentTab = 2; ML:ShowSendPanel()
if S.frame.toEditBox then S.frame.toEditBox:SetText(sender) end
if S.frame.subjectEditBox then
local re = subject or ""
if string.sub(re, 1, 4) ~= "Re: " then re = "Re: " .. re end
S.frame.subjectEditBox:SetText(re)
end
end
end)
S.frame.inboxPanel:Hide()
dp:Show()
end
function ML:HideMailDetail()
if not S.frame then return end
S.detailMailIndex = nil
if S.frame.detailPanel then S.frame.detailPanel:Hide() end
S.frame.inboxPanel:Show()
UpdateInbox()
end
--------------------------------------------------------------------------------
-- BUILD: Mail Bag panel (grid view of all inbox items)
--------------------------------------------------------------------------------
local function BuildMailBagPanel()
local f = S.frame
local panelTop = L.HEADER + 6 + L.TAB_H + 4
local bp = CreateFrame("Frame", nil, f)
bp:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -panelTop)
bp:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
bp:Hide()
f.bagPanel = bp
local font = GetFont()
local slotsPerPage = L.BAG_PER_ROW * L.BAG_ROWS
local infoFS = bp:CreateFontString(nil, "OVERLAY")
infoFS:SetFont(font, 10, "OUTLINE")
infoFS:SetPoint("TOPLEFT", bp, "TOPLEFT", L.PAD, -2)
infoFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
f.bagInfoFS = infoFS
local codLegend = bp:CreateFontString(nil, "OVERLAY")
codLegend:SetFont(font, 9, "OUTLINE")
codLegend:SetPoint("TOPRIGHT", bp, "TOPRIGHT", -L.PAD, -2)
codLegend:SetText("|cFFFF5555■|r 付款取信")
codLegend:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
f.bagSlots = {}
local gridTop = 16
for i = 1, slotsPerPage do
local row = math.floor((i - 1) / L.BAG_PER_ROW)
local col = math.mod((i - 1), L.BAG_PER_ROW)
local sf = CreateFrame("Button", "SFramesMailBagSlot" .. i, bp)
sf:SetWidth(L.BAG_SLOT); sf:SetHeight(L.BAG_SLOT)
sf:SetPoint("TOPLEFT", bp, "TOPLEFT",
L.PAD + col * (L.BAG_SLOT + L.BAG_GAP),
-(gridTop + row * (L.BAG_SLOT + L.BAG_GAP)))
sf: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 },
})
sf:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
sf:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
local ico = sf:CreateTexture(nil, "ARTWORK")
ico:SetTexCoord(0.08, 0.92, 0.08, 0.92)
ico:SetPoint("TOPLEFT", 3, -3); ico:SetPoint("BOTTOMRIGHT", -3, 3)
ico:Hide()
sf.icon = ico
local cnt = sf:CreateFontString(nil, "OVERLAY")
cnt:SetFont(font, 11, "OUTLINE")
cnt:SetPoint("BOTTOMRIGHT", sf, "BOTTOMRIGHT", -2, 2)
cnt:SetJustifyH("RIGHT")
sf.countFS = cnt
local moneyFS = sf:CreateFontString(nil, "OVERLAY")
moneyFS:SetFont(font, 8, "OUTLINE")
moneyFS:SetPoint("BOTTOM", sf, "BOTTOM", 0, 2)
moneyFS:SetWidth(L.BAG_SLOT)
moneyFS:SetJustifyH("CENTER")
moneyFS:Hide()
sf.moneyFS = moneyFS
local codFS = sf:CreateFontString(nil, "OVERLAY")
codFS:SetFont(font, 7, "OUTLINE")
codFS:SetPoint("TOP", sf, "TOP", 0, -1)
codFS:SetText("|cFFFF3333付款|r")
codFS:Hide()
sf.codFS = codFS
sf.mailData = nil
sf:RegisterForClicks("LeftButtonUp")
sf:SetScript("OnClick", function()
local data = this.mailData
if not data then return end
if data.codAmount and data.codAmount > 0 then
local codStr = FormatMoneyString(data.codAmount)
local idx = data.mailIndex
StaticPopupDialogs["NANAMI_MAIL_COD_CONFIRM"].OnAccept = function()
TakeInboxItem(idx)
end
StaticPopup_Show("NANAMI_MAIL_COD_CONFIRM", codStr)
elseif data.hasItem then
TakeInboxItem(data.mailIndex)
elseif data.money and data.money > 0 then
local idx = data.mailIndex
TakeInboxMoney(idx)
if not S.deleteTimer then S.deleteTimer = CreateFrame("Frame") end
S.deleteElapsed = 0
S.deleteTimer:SetScript("OnUpdate", function()
S.deleteElapsed = S.deleteElapsed + arg1
if S.deleteElapsed >= 0.5 then
this:SetScript("OnUpdate", nil)
if idx <= GetInboxNumItems() then DeleteInboxItem(idx) end
end
end)
end
end)
sf:SetScript("OnEnter", function()
this:SetBackdropBorderColor(T.slotHover[1], T.slotHover[2], T.slotHover[3], T.slotHover[4])
local data = this.mailData
if not data then return end
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
if data.hasItem then
pcall(GameTooltip.SetInboxItem, GameTooltip, data.mailIndex)
else
GameTooltip:AddLine("金币邮件", 1, 0.84, 0)
end
GameTooltip:AddLine(" ")
if data.sender then
GameTooltip:AddLine("发件人: " .. data.sender, 0.7, 0.7, 0.7)
end
if data.subject and data.subject ~= "" then
GameTooltip:AddLine(data.subject, 0.5, 0.5, 0.5)
end
if data.codAmount and data.codAmount > 0 then
GameTooltip:AddLine(" ")
GameTooltip:AddLine("付款取信: " .. FormatMoneyString(data.codAmount), 1, 0.3, 0.3)
GameTooltip:AddLine("|cFFFFCC00点击支付并取回|r")
elseif data.money and data.money > 0 and not data.hasItem then
GameTooltip:AddLine(" ")
GameTooltip:AddLine("金额: " .. FormatMoneyString(data.money), 1, 0.84, 0)
GameTooltip:AddLine("|cFFFFCC00点击收取金币|r")
elseif data.hasItem then
GameTooltip:AddLine(" ")
GameTooltip:AddLine("|cFFFFCC00点击收取物品|r")
end
GameTooltip:Show()
end)
sf:SetScript("OnLeave", function()
local data = this.mailData
if data and data.codAmount and data.codAmount > 0 then
this:SetBackdropBorderColor(1, 0.3, 0.3, 0.8)
else
this:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
end
GameTooltip:Hide()
end)
f.bagSlots[i] = sf
end
local bsep = bp:CreateTexture(nil, "ARTWORK")
bsep:SetTexture("Interface\\Buttons\\WHITE8X8"); bsep:SetHeight(1)
bsep:SetPoint("BOTTOMLEFT", bp, "BOTTOMLEFT", 6, L.BOTTOM)
bsep:SetPoint("BOTTOMRIGHT", bp, "BOTTOMRIGHT", -6, L.BOTTOM)
bsep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
local prev = CreateActionBtn(bp, "<", 28)
prev:SetHeight(22); prev:SetPoint("BOTTOMLEFT", bp, "BOTTOMLEFT", L.PAD, 12)
prev:SetScript("OnClick", function() S.bagPage = S.bagPage - 1; ML:UpdateMailBag() end)
f.bagPrevBtn = prev
local nxt = CreateActionBtn(bp, ">", 28)
nxt:SetHeight(22); nxt:SetPoint("LEFT", prev, "RIGHT", 4, 0)
nxt:SetScript("OnClick", function() S.bagPage = S.bagPage + 1; ML:UpdateMailBag() end)
f.bagNextBtn = nxt
local pageFS = bp:CreateFontString(nil, "OVERLAY")
pageFS:SetFont(font, 10, "OUTLINE")
pageFS:SetPoint("LEFT", nxt, "RIGHT", 8, 0)
pageFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
f.bagPageFS = pageFS
local colAll = CreateActionBtn(bp, "全部收取", 80)
colAll:SetHeight(24); colAll:SetPoint("BOTTOMRIGHT", bp, "BOTTOMRIGHT", -L.PAD, 10)
colAll:SetScript("OnClick", function()
if S.isCollecting then StopCollecting() else CollectAll() end
end)
f.bagCollectAllBtn = colAll
end
--------------------------------------------------------------------------------
-- Mail Bag: Update
--------------------------------------------------------------------------------
function ML:UpdateMailBag()
if not S.frame or not S.frame:IsVisible() or S.currentTab ~= 3 then return end
local slotsPerPage = L.BAG_PER_ROW * L.BAG_ROWS
local numMails = GetInboxNumItems()
local entries = {}
for mi = 1, numMails do
local _, _, sender, subject, money, CODAmount, daysLeft, hasItem = GetInboxHeaderInfo(mi)
if hasItem or (money and money > 0) or (CODAmount and CODAmount > 0) then
local itemName, itemTex
if hasItem then itemName, itemTex = GetInboxItem(mi) end
table.insert(entries, {
mailIndex = mi,
hasItem = hasItem,
itemName = itemName,
itemTexture = itemTex,
money = money or 0,
codAmount = CODAmount or 0,
sender = sender or "未知",
subject = subject or "",
daysLeft = daysLeft,
})
end
end
local totalEntries = table.getn(entries)
local totalPages = math.max(1, math.ceil(totalEntries / slotsPerPage))
if S.bagPage > totalPages then S.bagPage = totalPages end
if S.bagPage < 1 then S.bagPage = 1 end
S.frame.bagInfoFS:SetText(string.format("共 %d 件可收取 (%d 封邮件)", totalEntries, numMails))
S.frame.bagPageFS:SetText(string.format("第 %d/%d 页", S.bagPage, totalPages))
S.frame.bagPrevBtn:SetDisabled(S.bagPage <= 1)
S.frame.bagNextBtn:SetDisabled(S.bagPage >= totalPages)
S.frame.bagCollectAllBtn:SetDisabled(numMails == 0 and not S.isCollecting)
if S.isCollecting then
S.frame.bagCollectAllBtn.label:SetText("收取中...")
else
S.frame.bagCollectAllBtn.label:SetText("全部收取")
end
for i = 1, slotsPerPage do
local slot = S.frame.bagSlots[i]
local ei = (S.bagPage - 1) * slotsPerPage + i
local entry = entries[ei]
if entry then
slot.mailData = entry
if entry.hasItem and entry.itemTexture then
slot.icon:SetTexture(entry.itemTexture)
elseif entry.money > 0 then
slot.icon:SetTexture("Interface\\Icons\\INV_Misc_Coin_01")
else
slot.icon:SetTexture("Interface\\Icons\\INV_Misc_Note_01")
end
slot.icon:Show()
slot.countFS:SetText("")
slot.moneyFS:Hide()
if entry.money > 0 and not entry.hasItem then
local g = math.floor(entry.money / 10000)
if g > 0 then
slot.moneyFS:SetText("|cFFFFD700" .. g .. "g|r")
else
local sv = math.floor(math.mod(entry.money, 10000) / 100)
if sv > 0 then
slot.moneyFS:SetText("|cFFC7C7CF" .. sv .. "s|r")
else
local cv = math.mod(entry.money, 100)
slot.moneyFS:SetText("|cFFB87333" .. cv .. "c|r")
end
end
slot.moneyFS:Show()
end
if entry.codAmount > 0 then
slot.codFS:Show()
slot:SetBackdropBorderColor(1, 0.3, 0.3, 0.8)
else
slot.codFS:Hide()
slot:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
end
slot:Show()
else
slot.mailData = nil
slot.icon:Hide()
slot.countFS:SetText("")
slot.moneyFS:Hide()
slot.codFS:Hide()
slot:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
slot:Show()
end
end
end
--------------------------------------------------------------------------------
-- Mail Bag: Panel Switching
--------------------------------------------------------------------------------
function ML:ShowMailBagPanel()
if not S.frame then return end
S.frame.tabInbox:SetActive(false)
S.frame.tabBag:SetActive(true)
S.frame.tabSend:SetActive(false)
if S.frame.detailPanel then S.frame.detailPanel:Hide() end
S.detailMailIndex = nil
S.frame.inboxPanel:Hide()
S.frame.sendPanel:Hide()
S.frame.bagPanel:Show()
ML:UpdateMailBag()
end
--------------------------------------------------------------------------------
-- BUILD: Send panel
--------------------------------------------------------------------------------
local function BuildSendPanel()
local f = S.frame
local panelTop = L.HEADER + 6 + L.TAB_H + 4
local sp = CreateFrame("Frame", "SFramesMailSendPanel", f)
sp:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -panelTop)
sp:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
sp:EnableMouse(true)
sp:SetScript("OnReceiveDrag", function() AcceptCursorItem() end)
sp:SetScript("OnMouseUp", function()
if CursorHasItem() then AcceptCursorItem() end
end)
sp:Hide()
f.sendPanel = sp
local font = GetFont()
-- Recipient
local labelW = 50
local ebW = L.W - L.PAD * 2 - labelW - 6
local toLabel = sp:CreateFontString(nil, "OVERLAY")
toLabel:SetFont(font, 11, "OUTLINE"); toLabel:SetPoint("TOPLEFT", sp, "TOPLEFT", L.PAD, -32)
toLabel:SetWidth(labelW); toLabel:SetJustifyH("RIGHT")
toLabel:SetText("收件人:"); toLabel:SetTextColor(T.labelText[1], T.labelText[2], T.labelText[3])
local toEB = CreateStyledEditBox(sp, ebW, 22)
toEB:SetPoint("LEFT", toLabel, "RIGHT", 6, 0)
f.toEditBox = toEB
-- Contact buttons
local addContactBtn = CreateActionBtn(sp, "添加", 30)
addContactBtn:SetHeight(22); addContactBtn:SetPoint("TOPRIGHT", sp, "TOPRIGHT", -L.PAD, -6)
addContactBtn:SetScript("OnClick", function()
local name = f.toEditBox:GetText()
if name and name ~= "" then
if not SFramesDB then
SFramesDB = {}
end
if not SFramesDB.mailContacts then
SFramesDB.mailContacts = {}
end
SFramesDB.mailContacts[name] = true
DEFAULT_CHAT_FRAME:AddMessage("|cFF66FF88[Nanami-Mail]|r 已添加常用联系人: " .. name)
end
end)
-- Contact management frame
local contactFrame
local function CreateContactFrame()
if contactFrame then return end
contactFrame = CreateFrame("Frame", "SFramesMailContactFrame", UIParent)
if not contactFrame then
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 创建联系人管理窗口失败")
return
end
contactFrame:SetWidth(250)
contactFrame:SetHeight(300)
contactFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
contactFrame:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 12,
insets = { left = 3, right = 3, top = 3, bottom = 3 }
})
contactFrame:SetBackdropColor(0.1, 0.1, 0.1, 0.95)
contactFrame:SetBackdropBorderColor(0.5, 0.5, 0.5, 1)
contactFrame:SetFrameStrata("DIALOG")
contactFrame:SetMovable(true)
contactFrame:EnableMouse(true)
contactFrame:RegisterForDrag("LeftButton")
contactFrame:SetScript("OnDragStart", function() this:StartMoving() end)
contactFrame:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
contactFrame:Hide()
-- Title
local title = contactFrame:CreateFontString(nil, "OVERLAY")
title:SetFont(GetFont(), 12, "OUTLINE")
title:SetPoint("TOP", contactFrame, "TOP", 0, -10)
title:SetText("|cFFFFCC00常用联系人管理|r")
-- Close button
local closeBtn = CreateActionBtn(contactFrame, "X", 20)
closeBtn:SetHeight(20)
closeBtn:SetPoint("TOPRIGHT", contactFrame, "TOPRIGHT", -5, -5)
closeBtn:SetScript("OnClick", function() contactFrame:Hide() end)
-- Scroll frame
local scrollFrame = CreateFrame("ScrollFrame", nil, contactFrame)
if not scrollFrame then
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 创建滚动框架失败")
return
end
scrollFrame:SetWidth(230)
scrollFrame:SetHeight(240)
scrollFrame:SetPoint("TOPLEFT", contactFrame, "TOPLEFT", 10, -30)
local scrollChild = CreateFrame("Frame", nil, scrollFrame)
if not scrollChild then
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 创建滚动子框架失败")
return
end
scrollChild:SetWidth(210)
scrollChild:SetHeight(1)
scrollFrame:SetScrollChild(scrollChild)
-- Refresh function
contactFrame.Refresh = function()
if not SFramesDB then SFramesDB = {} end
if not SFramesDB.mailContacts then SFramesDB.mailContacts = {} end
-- Clear existing buttons
for _, child in ipairs({scrollChild:GetChildren()}) do
child:Hide()
child:SetParent(nil)
child = nil
end
-- Create contact buttons
local contacts = SFramesDB.mailContacts
local y = 0
for name in pairs(contacts) do
local contactBtn = CreateActionBtn(scrollChild, name, 180)
contactBtn:SetHeight(20)
contactBtn:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 0, -y)
contactBtn.contactName = name
contactBtn:SetScript("OnClick", function()
if this.contactName and f.toEditBox then
f.toEditBox:SetText(this.contactName)
contactFrame:Hide()
end
end)
local deleteBtn = CreateActionBtn(scrollChild, "×", 20)
deleteBtn:SetHeight(20)
deleteBtn:SetPoint("LEFT", contactBtn, "RIGHT", 5, 0)
deleteBtn.contactName = name
deleteBtn:SetScript("OnClick", function()
if this.contactName then
SFramesDB.mailContacts[this.contactName] = nil
contactFrame:Refresh()
end
end)
y = y + 25
end
scrollChild:SetHeight(math.max(1, y))
-- No contacts message
if not next(contacts) then
local noContacts = scrollChild:CreateFontString(nil, "OVERLAY")
noContacts:SetFont(GetFont(), 11, "OUTLINE")
noContacts:SetPoint("TOP", scrollChild, "TOP", 0, -10)
noContacts:SetText("|cFF999999常用联系人列表为空|r")
end
end
-- Add contact section
local addLabel = contactFrame:CreateFontString(nil, "OVERLAY")
addLabel:SetFont(GetFont(), 11, "OUTLINE")
addLabel:SetPoint("BOTTOMLEFT", contactFrame, "BOTTOMLEFT", 10, 40)
addLabel:SetText("添加新联系人:")
local addEditBox = CreateStyledEditBox(contactFrame, 150, 20)
addEditBox:SetPoint("BOTTOMLEFT", contactFrame, "BOTTOMLEFT", 10, 15)
local addBtn = CreateActionBtn(contactFrame, "添加", 60)
addBtn:SetHeight(20)
addBtn:SetPoint("BOTTOMRIGHT", contactFrame, "BOTTOMRIGHT", -10, 15)
addBtn:SetScript("OnClick", function()
local name = addEditBox:GetText()
if name and name ~= "" then
if not SFramesDB then SFramesDB = {} end
if not SFramesDB.mailContacts then SFramesDB.mailContacts = {} end
SFramesDB.mailContacts[name] = true
addEditBox:SetText("")
contactFrame:Refresh()
end
end)
end
local manageContactBtn = CreateActionBtn(sp, "管理", 30)
manageContactBtn:SetHeight(22); manageContactBtn:SetPoint("RIGHT", addContactBtn, "LEFT", -4, 0)
manageContactBtn:SetScript("OnClick", function()
CreateContactFrame()
if contactFrame then
contactFrame:Refresh()
contactFrame:Show()
else
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 无法创建联系人管理窗口")
end
end)
-- Autocomplete dropdown for recipient
local AC_MAX = 8
local acBox = CreateFrame("Frame", "SFramesMailAutoComplete", f)
acBox:SetWidth(ebW); acBox:SetFrameStrata("TOOLTIP")
acBox: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 },
})
acBox:SetBackdropColor(0.05, 0.05, 0.1, 0.95)
acBox:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], 0.9)
acBox:SetPoint("TOPLEFT", toEB, "BOTTOMLEFT", 0, -2)
acBox:Hide()
acBox:EnableMouse(true)
f.acBox = acBox
local acButtons = {}
for ai = 1, AC_MAX do
local btn = CreateFrame("Button", nil, acBox)
btn:SetHeight(18); btn:SetPoint("TOPLEFT", acBox, "TOPLEFT", 4, -4 - (ai - 1) * 18)
btn:SetPoint("RIGHT", acBox, "RIGHT", -4, 0)
btn:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
local bfs = btn:CreateFontString(nil, "OVERLAY")
bfs:SetFont(font, 11, "OUTLINE"); bfs:SetAllPoints(btn)
bfs:SetJustifyH("LEFT")
btn.label = bfs
btn.sourceName = nil
btn:SetScript("OnClick", function()
if btn.sourceName then
toEB:SetText(btn.sourceName)
end
acBox:Hide()
f.subjectEditBox:SetFocus()
end)
btn:Hide()
acButtons[ai] = btn
end
f.acButtons = acButtons
local friendCache = {}
local function BuildFriendCache()
friendCache = {}
for fi = 1, GetNumFriends() do
local name = GetFriendInfo(fi)
if name then friendCache[name] = true end
end
end
local function ShowSuggestions()
local input = toEB:GetText()
if not input or input == "" then acBox:Hide(); return end
local upper = string.upper(input)
BuildFriendCache()
local seen = { [UnitName("player")] = true }
local results = {}
local function addName(name, source)
if not name or name == "" or seen[name] then return end
if string.find(string.upper(name), upper, 1, true) == 1 then
table.insert(results, { name = name, source = source })
end
seen[name] = true
end
-- Add mail contacts first
if SFramesDB and SFramesDB.mailContacts then
for name in pairs(SFramesDB.mailContacts) do
addName(name, "contact")
end
end
for fi = 1, GetNumFriends() do
local name = GetFriendInfo(fi)
addName(name, "friend")
end
if GetNumGuildMembers then
for gi = 1, GetNumGuildMembers(true) do
local name = GetGuildRosterInfo(gi)
addName(name, "guild")
end
end
local count = math.min(table.getn(results), AC_MAX)
if count == 0 then acBox:Hide(); return end
if count == 1 and results[1].name == input then acBox:Hide(); return end
for ai = 1, AC_MAX do
local btn = acButtons[ai]
if ai <= count then
local r = results[ai]
local col = T.whoColor
local tag = ""
if r.source == "contact" then
col = { 1, 1, 0.3 }; tag = " |cFFFFCC00[常用]|r"
elseif r.source == "friend" then
col = T.friendColor; tag = " |cFF66FF88[好友]|r"
elseif r.source == "guild" then
col = T.guildColor; tag = " |cFF66CCFF[公会]|r"
end
btn.label:SetText("|cFF" .. string.format("%02x%02x%02x",
col[1] * 255, col[2] * 255, col[3] * 255) .. r.name .. "|r" .. tag)
btn.sourceName = r.name
btn:Show()
else
btn:Hide()
end
end
acBox:SetHeight(count * 18 + 8)
acBox:Show()
end
toEB:SetScript("OnTextChanged", function() ShowSuggestions() end)
toEB:SetScript("OnEscapePressed", function() acBox:Hide(); this:ClearFocus() end)
toEB:SetScript("OnEnterPressed", function()
acBox:Hide(); f.subjectEditBox:SetFocus()
end)
-- Subject (second line, aligned with recipient)
local subLabel = sp:CreateFontString(nil, "OVERLAY")
subLabel:SetFont(font, 11, "OUTLINE"); subLabel:SetPoint("TOPLEFT", sp, "TOPLEFT", L.PAD, -60)
subLabel:SetWidth(labelW); subLabel:SetJustifyH("RIGHT")
subLabel:SetText("主题:"); subLabel:SetTextColor(T.labelText[1], T.labelText[2], T.labelText[3])
local subEB = CreateStyledEditBox(sp, ebW, 22)
subEB:SetPoint("LEFT", subLabel, "RIGHT", 6, 0)
f.subjectEditBox = subEB
toEB:SetScript("OnTabPressed", function() acBox:Hide(); f.subjectEditBox:SetFocus() end)
subEB:SetScript("OnTabPressed", function() f.toEditBox:SetFocus() end)
-- Body
local bodyLabel = sp:CreateFontString(nil, "OVERLAY")
bodyLabel:SetFont(font, 11, "OUTLINE"); bodyLabel:SetPoint("TOPLEFT", sp, "TOPLEFT", L.PAD, -88)
bodyLabel:SetText("正文:"); bodyLabel:SetTextColor(T.labelText[1], T.labelText[2], T.labelText[3])
local bsf = CreateFrame("ScrollFrame", "SFramesMailBodyScroll", sp, "UIPanelScrollFrameTemplate")
bsf:SetPoint("TOPLEFT", bodyLabel, "BOTTOMLEFT", 0, -4)
bsf:SetWidth(L.W - L.PAD * 2 - 28); bsf:SetHeight(100)
bsf: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 },
})
bsf:SetBackdropColor(T.inputBg[1], T.inputBg[2], T.inputBg[3], T.inputBg[4] or 0.95)
bsf:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], 0.8)
local bodyEB = CreateFrame("EditBox", "SFramesMailBodyEditBox", bsf)
bodyEB:SetWidth(L.W - L.PAD * 2 - 40); bodyEB:SetHeight(200)
bodyEB:SetFont(font, 11, "OUTLINE"); bodyEB:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
bodyEB:SetAutoFocus(false); bodyEB:SetMultiLine(true); bodyEB:SetMaxLetters(500)
bodyEB:SetTextInsets(6, 6, 4, 4)
bsf:SetScrollChild(bodyEB)
f.bodyEditBox = bodyEB
-- Money mode toggle (附加金币 / 付款取信)
local mToggle = CreateActionBtn(sp, "附加金币", 72)
mToggle:SetHeight(20); mToggle:SetPoint("TOPLEFT", bsf, "BOTTOMLEFT", 0, -10)
f.moneyToggle = mToggle
local function UpdateMoneyToggle()
if S.codMode then
mToggle.label:SetText("|cFFFF5555付款取信|r")
mToggle:SetBackdropBorderColor(1, 0.3, 0.3, 0.8)
else
mToggle.label:SetText("附加金币")
mToggle:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
end
end
f.UpdateMoneyToggle = UpdateMoneyToggle
mToggle:SetScript("OnClick", function()
S.codMode = not S.codMode; UpdateMoneyToggle()
end)
mToggle:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_TOPRIGHT")
if S.codMode then
GameTooltip:AddLine("付款取信模式", 1, 0.5, 0.5)
GameTooltip:AddLine("收件人需支付指定金额才能取回附件", 0.8, 0.8, 0.8)
else
GameTooltip:AddLine("附加金币模式", 1, 0.84, 0)
GameTooltip:AddLine("随邮件附送金币给收件人", 0.8, 0.8, 0.8)
end
GameTooltip:AddLine("|cFFFFCC00点击切换模式|r")
GameTooltip:Show()
end)
mToggle:SetScript("OnLeave", function() GameTooltip:Hide() end)
UpdateMoneyToggle()
local gL = sp:CreateFontString(nil, "OVERLAY")
gL:SetFont(font, 10, "OUTLINE"); gL:SetPoint("LEFT", mToggle, "RIGHT", 6, 0)
gL:SetText(""); gL:SetTextColor(T.moneyGold[1], T.moneyGold[2], T.moneyGold[3])
local gEB = CreateStyledEditBox(sp, 50, 20, true); gEB:SetPoint("LEFT", gL, "RIGHT", 4, 0); gEB:SetText("0"); f.goldEB = gEB
local sL = sp:CreateFontString(nil, "OVERLAY")
sL:SetFont(font, 10, "OUTLINE"); sL:SetPoint("LEFT", gEB, "RIGHT", 6, 0)
sL:SetText(""); sL:SetTextColor(T.moneySilver[1], T.moneySilver[2], T.moneySilver[3])
local sEB = CreateStyledEditBox(sp, 36, 20, true); sEB:SetPoint("LEFT", sL, "RIGHT", 4, 0); sEB:SetText("0"); f.silverEB = sEB
local cL = sp:CreateFontString(nil, "OVERLAY")
cL:SetFont(font, 10, "OUTLINE"); cL:SetPoint("LEFT", sEB, "RIGHT", 6, 0)
cL:SetText(""); cL:SetTextColor(T.moneyCopper[1], T.moneyCopper[2], T.moneyCopper[3])
local cEB = CreateStyledEditBox(sp, 36, 20, true); cEB:SetPoint("LEFT", cL, "RIGHT", 4, 0); cEB:SetText("0"); f.copperEB = cEB
-- Attachments
local aLabel = sp:CreateFontString(nil, "OVERLAY")
aLabel:SetFont(font, 11, "OUTLINE"); aLabel:SetPoint("TOPLEFT", mToggle, "TOPLEFT", 0, -28)
aLabel:SetText("附件 (右击/拖放背包物品添加):"); aLabel:SetTextColor(T.labelText[1], T.labelText[2], T.labelText[3])
local clrBtn = CreateActionBtn(sp, "清空", 50)
clrBtn:SetHeight(20); clrBtn:SetPoint("LEFT", aLabel, "RIGHT", 8, 0)
clrBtn:SetScript("OnClick", function() ClearSendItems(); ML:UpdateSendPanel() end)
-- Item slot grid
f.sendItemSlots = {}
BuildSendSlots(sp, aLabel, f)
-- Send button & status
local status = sp:CreateFontString(nil, "OVERLAY")
status:SetFont(font, 11, "OUTLINE"); status:SetPoint("BOTTOMLEFT", sp, "BOTTOMLEFT", L.PAD, 14)
status:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
f.sendStatus = status
local sendBtn = CreateActionBtn(sp, "发送", 80)
sendBtn:SetHeight(26); sendBtn:SetPoint("BOTTOMRIGHT", sp, "BOTTOMRIGHT", -L.PAD, 10)
sendBtn:SetScript("OnClick", function()
local r = f.toEditBox:GetText()
local sub = f.subjectEditBox:GetText()
local bd = f.bodyEditBox:GetText()
local g = tonumber(f.goldEB:GetText()) or 0
local sv = tonumber(f.silverEB:GetText()) or 0
local c = tonumber(f.copperEB:GetText()) or 0
DoMultiSend(r, sub, bd, g * 10000 + sv * 100 + c)
end)
f.sendBtn = sendBtn
end
--------------------------------------------------------------------------------
-- BUILD: Send item slots (separate function to stay under upvalue limit)
--------------------------------------------------------------------------------
function BuildSendSlots(parent, anchor, f)
local slotSize, slotGap, perRow = 34, 4, 6
for i = 1, L.MAX_SEND do
local row = math.floor((i - 1) / perRow)
local col = math.mod((i - 1), perRow)
local sf = CreateFrame("Button", "SFramesMailSendSlot" .. i, parent)
sf:SetWidth(slotSize); sf:SetHeight(slotSize)
sf:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", col * (slotSize + slotGap), -(4 + row * (slotSize + slotGap)))
sf: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 },
})
sf:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
sf:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
local ico = sf:CreateTexture(nil, "ARTWORK")
ico:SetTexCoord(0.08, 0.92, 0.08, 0.92)
ico:SetPoint("TOPLEFT", 3, -3); ico:SetPoint("BOTTOMRIGHT", -3, 3); ico:Hide()
sf.icon = ico
local cnt = sf:CreateFontString(nil, "OVERLAY")
cnt:SetFont("Fonts\\ARIALN.TTF", 11, "OUTLINE")
cnt:SetPoint("BOTTOMRIGHT", sf, "BOTTOMRIGHT", -2, 2); cnt:SetJustifyH("RIGHT")
sf.countFS = cnt
local rb = CreateFrame("Button", nil, sf)
rb:SetWidth(14); rb:SetHeight(14); rb:SetPoint("TOPRIGHT", sf, "TOPRIGHT", 2, 2)
local rtx = rb:CreateTexture(nil, "OVERLAY")
rtx:SetTexture("Interface\\AddOns\\Nanami-UI\\img\\icon")
rtx:SetTexCoord(0.25, 0.375, 0, 0.125); rtx:SetAllPoints(); rtx:SetVertexColor(1, 0.4, 0.4)
rb:Hide(); sf.removeBtn = rb; sf.hasItem = false
local si = i
rb:SetScript("OnClick", function() RemoveSendItem(si); ML:UpdateSendPanel() end)
sf:SetScript("OnReceiveDrag", function() AcceptCursorItem() end)
sf:SetScript("OnClick", function()
if CursorHasItem() then AcceptCursorItem(); return end
if arg1 == "RightButton" and sf.hasItem then RemoveSendItem(si); ML:UpdateSendPanel() end
end)
sf:RegisterForClicks("LeftButtonUp", "RightButtonUp")
sf:SetScript("OnEnter", function()
this:SetBackdropBorderColor(T.slotHover[1], T.slotHover[2], T.slotHover[3], T.slotHover[4])
local e = S.sendQueue[si]
if e and e.link then
GameTooltip:SetOwner(this, "ANCHOR_RIGHT"); pcall(GameTooltip.SetHyperlink, GameTooltip, e.link); GameTooltip:Show()
end
end)
sf:SetScript("OnLeave", function()
this:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
GameTooltip:Hide()
end)
f.sendItemSlots[i] = sf
end
end
--------------------------------------------------------------------------------
-- BUILD: Events & hooks
--------------------------------------------------------------------------------
local function SetupEvents()
local f = S.frame
f:SetScript("OnHide", function()
StopCollecting(); if S.multiSend then AbortMultiSend() end; pcall(CloseMail)
end)
f:SetScript("OnEvent", function()
if event == "MAIL_SHOW" then
if SFramesDB and SFramesDB.enableMail == false then return end
S.currentTab = 1; S.inboxPage = 1; S.bagPage = 1; S.inboxChecked = {}
CheckInbox(); f:Show(); ML:ShowInboxPanel()
elseif event == "MAIL_INBOX_UPDATE" then
if f:IsVisible() then
if S.detailMailIndex and f.detailPanel and f.detailPanel:IsVisible() then
if S.detailMailIndex <= GetInboxNumItems() then
ML:ShowMailDetail(S.detailMailIndex)
else
ML:HideMailDetail()
end
elseif S.currentTab == 3 then
ML:UpdateMailBag()
else
UpdateInbox()
end
end
-- 收件箱清空后同步小地图信件图标状态
if MiniMapMailFrame then
if HasNewMail and HasNewMail() then
MiniMapMailFrame:Show()
elseif GetInboxNumItems() == 0 then
MiniMapMailFrame:Hide()
end
end
elseif event == "UPDATE_PENDING_MAIL" then
if MiniMapMailFrame then
if HasNewMail and HasNewMail() then
MiniMapMailFrame:Show()
else
MiniMapMailFrame:Hide()
end
end
elseif event == "MAIL_CLOSED" then
if S.multiSend then AbortMultiSend("邮箱已关闭") end
f:Hide()
elseif event == "MAIL_SEND_SUCCESS" then
if S.multiSend then
S.multiSend.sendOk = true
S.multiSend.phase = "cooldown"
S.multiSend.elapsed = 0
else
FlashStatus("发送成功!", T.successText, 3)
ResetSendForm()
if f.sendBtn then f.sendBtn.label:SetText("发送"); f.sendBtn:SetDisabled(false) end
end
elseif event == "MAIL_SEND_INFO_UPDATE" then
-- noop
elseif event == "MAIL_FAILED" then
if S.multiSend and S.multiSend.phase == "wait_send" and not S.multiSend.sendOk then
AbortMultiSend("发送失败")
elseif not S.multiSend then
if f.sendBtn then f.sendBtn.label:SetText("发送"); f.sendBtn:SetDisabled(false) end
FlashStatus("发送失败!", T.errorText, 5)
end
end
end)
f:RegisterEvent("MAIL_SHOW"); f:RegisterEvent("MAIL_INBOX_UPDATE")
f:RegisterEvent("MAIL_CLOSED"); f:RegisterEvent("MAIL_SEND_SUCCESS")
f:RegisterEvent("MAIL_SEND_INFO_UPDATE"); f:RegisterEvent("MAIL_FAILED")
f:RegisterEvent("UPDATE_PENDING_MAIL")
if MailFrame then
local origMailOnShow = MailFrame:GetScript("OnShow")
MailFrame:SetScript("OnShow", function()
if origMailOnShow then origMailOnShow() end
this:ClearAllPoints()
this:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -10000, 10000)
this:SetAlpha(0)
this:EnableMouse(false)
end)
for i = table.getn(UISpecialFrames), 1, -1 do
if UISpecialFrames[i] == "MailFrame" then
table.remove(UISpecialFrames, i)
end
end
end
if OpenMailFrame then
OpenMailFrame:UnregisterAllEvents(); OpenMailFrame:Hide()
end
tinsert(UISpecialFrames, "SFramesMailFrame")
SFrames.Mail.TryAddItemFromBag = function(bag, slot)
if S.frame and S.frame:IsVisible() and not S.isSending then
if bag and slot then
if S.currentTab ~= 2 then S.currentTab = 2; ML:ShowSendPanel() end
AddSendItem(bag, slot); ML:UpdateSendPanel()
return true
end
end
return false
end
if ContainerFrameItemButton_OnClick then
local orig = ContainerFrameItemButton_OnClick
ContainerFrameItemButton_OnClick = function(button, ignoreShift)
if arg1 == "RightButton" then
if SFrames.Mail.TryAddItemFromBag(this:GetParent():GetID(), this:GetID()) then return end
end
orig(button, ignoreShift)
end
end
end
--------------------------------------------------------------------------------
-- Initialize (calls sub-builders)
--------------------------------------------------------------------------------
function ML:Initialize()
if S.frame then return end
BuildMainFrame()
BuildInboxPanel()
BuildDetailPanel()
BuildMailBagPanel()
BuildSendPanel()
SetupEvents()
end
--------------------------------------------------------------------------------
-- Panel Switching
--------------------------------------------------------------------------------
function ML:ShowInboxPanel()
if not S.frame then return end
S.frame.tabInbox:SetActive(true); S.frame.tabBag:SetActive(false); S.frame.tabSend:SetActive(false)
if S.frame.detailPanel then S.frame.detailPanel:Hide() end
S.frame.inboxPanel:Show(); S.frame.sendPanel:Hide(); S.frame.bagPanel:Hide()
S.detailMailIndex = nil
UpdateInbox()
end
function ML:ShowSendPanel()
if not S.frame then return end
S.frame.tabInbox:SetActive(false); S.frame.tabBag:SetActive(false); S.frame.tabSend:SetActive(true)
if S.frame.detailPanel then S.frame.detailPanel:Hide() end
S.detailMailIndex = nil
S.frame.inboxPanel:Hide(); S.frame.sendPanel:Show(); S.frame.bagPanel:Hide()
if S.frame.sendStatus then S.frame.sendStatus:SetText("") end
if S.statusFadeTimer then S.statusFadeTimer:SetScript("OnUpdate", nil) end
ML:UpdateSendPanel()
end
--------------------------------------------------------------------------------
-- Slash commands for mail contacts
--------------------------------------------------------------------------------
SLASH_MAILCONTACT1 = "/mailcontact"
SLASH_MAILCONTACT2 = "/mailcontacts"
SlashCmdList["MAILCONTACT"] = function(msg)
if not SFramesDB then
SFramesDB = {}
end
if not SFramesDB.mailContacts then
SFramesDB.mailContacts = {}
end
local cmd, arg = string.match(msg, "^(%S*)%s*(.-)$")
cmd = string.lower(cmd)
if cmd == "add" and arg ~= "" then
SFramesDB.mailContacts[arg] = true
DEFAULT_CHAT_FRAME:AddMessage("|cFF66FF88[Nanami-Mail]|r 已添加常用联系人: " .. arg)
elseif cmd == "remove" and arg ~= "" then
if SFramesDB.mailContacts[arg] then
SFramesDB.mailContacts[arg] = nil
DEFAULT_CHAT_FRAME:AddMessage("|cFF66FF88[Nanami-Mail]|r 已删除常用联系人: " .. arg)
else
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[Nanami-Mail]|r 常用联系人中不存在: " .. arg)
end
elseif cmd == "list" then
local contacts = SFramesDB.mailContacts
if next(contacts) then
DEFAULT_CHAT_FRAME:AddMessage("|cFFFFCC00[Nanami-Mail]|r 常用联系人列表:")
for name in pairs(contacts) do
DEFAULT_CHAT_FRAME:AddMessage("|cFF66FF88- |r" .. name)
end
else
DEFAULT_CHAT_FRAME:AddMessage("|cFFFFCC00[Nanami-Mail]|r 常用联系人列表为空")
end
elseif cmd == "clear" then
SFramesDB.mailContacts = {}
DEFAULT_CHAT_FRAME:AddMessage("|cFF66FF88[Nanami-Mail]|r 已清空常用联系人列表")
else
DEFAULT_CHAT_FRAME:AddMessage("|cFFFFCC00[Nanami-Mail]|r 常用联系人命令:")
DEFAULT_CHAT_FRAME:AddMessage("|cFF66FF88/mailcontact add <name>|r - 添加常用联系人")
DEFAULT_CHAT_FRAME:AddMessage("|cFF66FF88/mailcontact remove <name>|r - 删除常用联系人")
DEFAULT_CHAT_FRAME:AddMessage("|cFF66FF88/mailcontact list|r - 查看常用联系人列表")
DEFAULT_CHAT_FRAME:AddMessage("|cFF66FF88/mailcontact clear|r - 清空常用联系人列表")
end
end
--------------------------------------------------------------------------------
-- Bootstrap
--------------------------------------------------------------------------------
local bootstrap = CreateFrame("Frame")
bootstrap:RegisterEvent("PLAYER_LOGIN")
bootstrap:SetScript("OnEvent", function()
if event == "PLAYER_LOGIN" then
if SFramesDB.enableMail == nil then SFramesDB.enableMail = true end
if SFramesDB.enableMail ~= false then ML:Initialize() end
end
end)