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

1634 lines
68 KiB
Lua

--------------------------------------------------------------------------------
-- 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 {}
-- 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,
inboxChecked = {},
collectQueue = {},
collectTimer = nil,
isCollecting = false,
sendQueue = {},
isSending = false,
collectElapsed = 0,
multiSend = nil, -- active multi-send state table
}
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,
}
--------------------------------------------------------------------------------
-- 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)
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("COD:"); 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)) and (not CODAmount or CODAmount == 0)
row.takeBtn:SetDisabled(not canTake)
row.takeBtn:SetScript("OnClick", function()
if row.mailIndex then
if hasItem then
TakeInboxItem(row.mailIndex)
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
UpdateInbox()
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()
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
if table.getn(items) == 0 then
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,
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 only on first mail
if ms.sentCount == 1 and ms.money and ms.money > 0 then
SetSendMailMoney(ms.money)
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, "收件箱", 70)
tabInbox:SetPoint("TOPLEFT", f, "TOPLEFT", L.PAD, -(L.HEADER + 6))
tabInbox:SetScript("OnClick", function() S.currentTab = 1; ML:ShowInboxPanel() end)
f.tabInbox = tabInbox
local tabSend = CreateTabBtn(f, "发送", 70)
tabSend:SetPoint("LEFT", tabInbox, "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("COD:"); dp.detailCodLabel:Show()
dp.detailCod:SetMoney(CODAmount); dp.detailCod:Show()
end
-- Take items button
local canTakeItem = hasItem and (not CODAmount or CODAmount == 0)
dp.takeItemBtn:SetDisabled(not canTakeItem)
dp.takeItemBtn:SetScript("OnClick", function()
if S.detailMailIndex then
TakeInboxItem(S.detailMailIndex)
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: 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, -6)
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
-- 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
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 == "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, -32)
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, -58)
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 row
local mLabel = sp:CreateFontString(nil, "OVERLAY")
mLabel:SetFont(font, 11, "OUTLINE"); mLabel:SetPoint("TOPLEFT", bsf, "BOTTOMLEFT", 0, -10)
mLabel:SetText("附加金币:"); mLabel:SetTextColor(T.labelText[1], T.labelText[2], T.labelText[3])
local gL = sp:CreateFontString(nil, "OVERLAY")
gL:SetFont(font, 10, "OUTLINE"); gL:SetPoint("LEFT", mLabel, "RIGHT", 6, 0)
gL:SetText(""); gL:SetTextColor(T.moneyGold[1], T.moneyGold[2], T.moneyGold[3])
local gEB = CreateStyledEditBox(sp, 60, 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, 40, 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, 40, 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", mLabel, "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.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
else
UpdateInbox()
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")
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: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()
BuildSendPanel()
SetupEvents()
end
--------------------------------------------------------------------------------
-- Panel Switching
--------------------------------------------------------------------------------
function ML:ShowInboxPanel()
if not S.frame then return end
S.frame.tabInbox:SetActive(true); S.frame.tabSend:SetActive(false)
if S.frame.detailPanel then S.frame.detailPanel:Hide() end
S.frame.inboxPanel:Show(); S.frame.sendPanel:Hide()
S.detailMailIndex = nil
UpdateInbox()
end
function ML:ShowSendPanel()
if not S.frame then return end
S.frame.tabInbox: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()
if S.frame.sendStatus then S.frame.sendStatus:SetText("") end
if S.statusFadeTimer then S.statusFadeTimer:SetScript("OnUpdate", nil) end
ML:UpdateSendPanel()
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)