feat: mailbox bug

This commit is contained in:
Salikh Gurgenidze
2025-12-27 01:59:14 +04:00
parent c14a3dbffd
commit 61bb1d9d8e
12 changed files with 951 additions and 5 deletions
+21
View File
@@ -131,6 +131,7 @@ function DB:Initialize()
money = 0, money = 0,
bags = {}, bags = {},
bank = {}, bank = {},
mailbox = {}, -- Add mailbox storage
equipped = {}, -- Add equipped items storage equipped = {}, -- Add equipped items storage
character = {}, -- Add character info storage character = {}, -- Add character info storage
lastUpdate = time(), lastUpdate = time(),
@@ -153,6 +154,10 @@ function DB:Initialize()
char.character = {} char.character = {}
addon:Debug("Added character field to existing character") addon:Debug("Added character field to existing character")
end end
if not char.mailbox then
char.mailbox = {}
addon:Debug("Added mailbox field to existing character")
end
end end
addon:Debug("Database initialized for %s", fullName) addon:Debug("Database initialized for %s", fullName)
@@ -215,6 +220,16 @@ function DB:SaveMoney(copper)
end end
end end
-- Save mailbox data
function DB:SaveMailbox(mailboxData)
local char = self:GetCurrentCharacter()
if char then
char.mailbox = mailboxData
char.lastUpdate = time()
addon:Debug("Saved mailbox data")
end
end
-- Get all characters (optionally filter by faction and/or realm) -- Get all characters (optionally filter by faction and/or realm)
function DB:GetAllCharacters(sameFactionOnly, currentRealmOnly) function DB:GetAllCharacters(sameFactionOnly, currentRealmOnly)
local chars = {} local chars = {}
@@ -256,6 +271,12 @@ function DB:GetCharacterBank(fullName)
return char and char.bank or {} return char and char.bank or {}
end end
-- Get character's mailbox
function DB:GetCharacterMailbox(fullName)
local char = Guda_DB.characters[fullName]
return char and char.mailbox or {}
end
-- Get character's equipped items -- Get character's equipped items
function DB:GetCharacterEquipped(fullName) function DB:GetCharacterEquipped(fullName)
local char = Guda_DB.characters[fullName] local char = Guda_DB.characters[fullName]
+8
View File
@@ -79,3 +79,11 @@ end
function Events:OnPlayerLogout(callback, owner) function Events:OnPlayerLogout(callback, owner)
self:Register("PLAYER_LOGOUT", callback, owner) self:Register("PLAYER_LOGOUT", callback, owner)
end end
function Events:OnMailShow(callback, owner)
self:Register("MAIL_SHOW", callback, owner)
end
function Events:OnMailClosed(callback, owner)
self:Register("MAIL_CLOSED", callback, owner)
end
+2
View File
@@ -102,11 +102,13 @@ addon.Modules = {
Tooltip = {}, Tooltip = {},
BagScanner = {}, BagScanner = {},
BankScanner = {}, BankScanner = {},
MailboxScanner = {},
MoneyTracker = {}, MoneyTracker = {},
EquipmentScanner = {}, EquipmentScanner = {},
SortEngine = {}, SortEngine = {},
BagFrame = {}, BagFrame = {},
BankFrame = {}, BankFrame = {},
MailboxFrame = {},
QuestItemBar = {}, QuestItemBar = {},
TrackedItemBar = {}, TrackedItemBar = {},
SettingsPopup = {}, SettingsPopup = {},
+7
View File
@@ -18,6 +18,7 @@ function Main:Initialize()
-- Initialize scanners -- Initialize scanners
addon.Modules.BagScanner:Initialize() addon.Modules.BagScanner:Initialize()
addon.Modules.BankScanner:Initialize() addon.Modules.BankScanner:Initialize()
addon.Modules.MailboxScanner:Initialize()
addon.Modules.MoneyTracker:Initialize() addon.Modules.MoneyTracker:Initialize()
addon.Modules.EquipmentScanner:Initialize() addon.Modules.EquipmentScanner:Initialize()
@@ -25,6 +26,7 @@ function Main:Initialize()
addon:Print("Initializing UI...") addon:Print("Initializing UI...")
addon.Modules.BagFrame:Initialize() addon.Modules.BagFrame:Initialize()
addon.Modules.BankFrame:Initialize() addon.Modules.BankFrame:Initialize()
addon.Modules.MailboxFrame:Initialize()
addon:Debug("Checking QuestItemBar module...") addon:Debug("Checking QuestItemBar module...")
if addon.Modules.QuestItemBar and addon.Modules.QuestItemBar.isLoaded then if addon.Modules.QuestItemBar and addon.Modules.QuestItemBar.isLoaded then
@@ -103,6 +105,10 @@ function Main:SetupSlashCommands()
-- Toggle bank -- Toggle bank
addon.Modules.BankFrame:Toggle() addon.Modules.BankFrame:Toggle()
elseif msg == "mail" or msg == "mailbox" then
-- Toggle mailbox
addon.Modules.MailboxFrame:Toggle()
elseif msg == "sort" then elseif msg == "sort" then
-- Sort bags -- Sort bags
addon.Modules.SortEngine:SortBags() addon.Modules.SortEngine:SortBags()
@@ -143,6 +149,7 @@ function Main:SetupSlashCommands()
addon:Print("Commands:") addon:Print("Commands:")
addon:Print("/guda - Toggle bags") addon:Print("/guda - Toggle bags")
addon:Print("/guda bank - Toggle bank") addon:Print("/guda bank - Toggle bank")
addon:Print("/guda mail - Toggle mailbox")
addon:Print("/guda sort - Sort bags") addon:Print("/guda sort - Sort bags")
addon:Print("/guda sortbank - Sort bank") addon:Print("/guda sortbank - Sort bank")
addon:Print("/guda track - Toggle item tracking") addon:Print("/guda track - Toggle item tracking")
+126
View File
@@ -0,0 +1,126 @@
-- Guda Mailbox Scanner
-- Scans and stores mailbox contents
local addon = Guda
local MailboxScanner = {}
addon.Modules.MailboxScanner = MailboxScanner
local mailboxOpen = false
-- Scan all mailbox items and return data
function MailboxScanner:ScanMailbox()
if not mailboxOpen then
addon:Debug("Cannot scan mailbox - not open")
return {}
end
local mailboxData = {}
local numItems = GetInboxNumItems()
for i = 1, numItems do
mailboxData[i] = self:ScanMailItem(i)
end
return mailboxData
end
-- Scan a single mail item
function MailboxScanner:ScanMailItem(index)
-- GetInboxItem(index) returns: packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM
local packageIcon, stationeryIcon, sender, subject, money, CODAmount, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM = GetInboxItem(index)
local itemData = nil
if hasItem then
addon:Debug("Scanning mail item %d", index)
-- In 1.12.1, GetInboxItemLink(index) returns the link
local itemLink = GetInboxItemLink(index)
-- GetInboxItemInfo(index) returns: name, texture, count, quality, canUse
local infoName, infoTexture, infoCount, infoQuality, infoCanUse = GetInboxItemInfo(index)
addon:Debug("Mail item %d: infoName=%s, infoTexture=%s", index, tostring(infoName), tostring(infoTexture))
-- Even if link is missing (not cached), we can store what we have from GetInboxItemInfo
itemData = {
link = itemLink,
texture = infoTexture or packageIcon or "Interface\\Icons\\INV_Misc_Bag_08",
count = infoCount or 1,
quality = infoQuality or 0,
name = infoName or subject or "Unknown Item",
}
-- If we have a link, try to get more detailed info
if itemLink then
local itemName, link, itemQuality, iLevel, itemCategory, itemType, itemStackCount, itemSubType, itemTexture, itemEquipLoc, itemSellPrice = addon.Modules.Utils:GetItemInfo(itemLink)
if itemName then
itemData.name = itemName
itemData.quality = itemQuality or itemData.quality
itemData.iLevel = iLevel
itemData.type = itemType
itemData.class = itemCategory
itemData.subclass = itemSubType
itemData.equipSlot = itemEquipLoc
if itemTexture then itemData.texture = itemTexture end
end
end
end
return {
sender = sender,
subject = subject,
money = money,
CODAmount = CODAmount,
daysLeft = daysLeft,
hasItem = hasItem,
item = itemData,
wasRead = wasRead,
packageIcon = packageIcon,
}
end
-- Save current mailbox to database
function MailboxScanner:SaveToDatabase()
if not mailboxOpen then
return
end
local mailboxData = self:ScanMailbox()
addon.Modules.DB:SaveMailbox(mailboxData)
addon:Debug("Mailbox data saved")
end
-- Initialize mailbox scanner
function MailboxScanner:Initialize()
-- Mailbox opened
addon.Modules.Events:OnMailShow(function()
mailboxOpen = true
addon:Debug("Mailbox opened")
-- Delay scan slightly to ensure item info is available
local frame = CreateFrame("Frame")
local elapsed = 0
frame:SetScript("OnUpdate", function()
elapsed = elapsed + arg1
if elapsed >= 0.5 then
frame:SetScript("OnUpdate", nil)
if mailboxOpen then
MailboxScanner:SaveToDatabase()
end
end
end)
end, "MailboxScanner")
-- Mailbox closed
addon.Modules.Events:OnMailClosed(function()
-- Final save on close
self:SaveToDatabase()
mailboxOpen = false
addon:Debug("Mailbox closed")
end, "MailboxScanner")
end
-- Check if mailbox is currently open
function MailboxScanner:IsMailboxOpen()
return mailboxOpen
end
+3
View File
@@ -16,6 +16,7 @@ Core\Tooltip.lua
Data\BagScanner.lua Data\BagScanner.lua
Data\BankScanner.lua Data\BankScanner.lua
Data\MailboxScanner.lua
Data\MoneyTracker.lua Data\MoneyTracker.lua
Data\EquipmentScanner.lua Data\EquipmentScanner.lua
@@ -25,12 +26,14 @@ Sorting\SortEngine.lua
UI\ItemButton.lua UI\ItemButton.lua
UI\BagFrame.lua UI\BagFrame.lua
UI\BankFrame.lua UI\BankFrame.lua
UI\MailboxFrame.lua
UI\QuestItemBar.lua UI\QuestItemBar.lua
UI\TrackedItemBar.lua UI\TrackedItemBar.lua
UI\SettingsPopup.lua UI\SettingsPopup.lua
UI\ItemButton.xml UI\ItemButton.xml
UI\BagFrame.xml UI\BagFrame.xml
UI\BankFrame.xml UI\BankFrame.xml
UI\MailboxFrame.xml
UI\SettingsPopup.xml UI\SettingsPopup.xml
Core\Main.lua Core\Main.lua
+113
View File
@@ -1578,6 +1578,119 @@ local function HideCharacterDropdown()
end end
end end
-- Toggle mail dropdown
function Guda_BagFrame_ToggleMailDropdown(button)
-- Hide character dropdown if it's shown
if characterDropdown and characterDropdown:IsShown() then
characterDropdown:Hide()
end
if bankDropdown and bankDropdown:IsShown() then
bankDropdown:Hide()
end
if mailDropdown and mailDropdown:IsShown() then
mailDropdown:Hide()
return
end
if not mailDropdown then
-- Create dropdown frame
mailDropdown = CreateFrame("Frame", "Guda_MailDropdown", UIParent)
mailDropdown:SetFrameStrata("DIALOG")
mailDropdown:SetWidth(200)
mailDropdown:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
tile = true,
tileSize = 16,
edgeSize = 16,
insets = { left = 4, right = 4, top = 4, bottom = 4 }
})
mailDropdown:SetBackdropColor(0, 0, 0, 0.95)
mailDropdown:EnableMouse(true)
mailDropdown:Hide()
mailDropdown.buttons = {}
end
-- Position dropdown below the button
mailDropdown:ClearAllPoints()
mailDropdown:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, -2)
-- Clear existing buttons
for _, btn in ipairs(mailDropdown.buttons) do
btn:Hide()
end
mailDropdown.buttons = {}
-- Get all characters on current realm
local chars = addon.Modules.DB:GetAllCharacters(false, true)
local yOffset = -8
-- Add character buttons
for _, char in ipairs(chars) do
-- Capture variables in local scope for closure
local charFullName = char.fullName
local charName = char.name
local charClassToken = char.classToken
local charButton = CreateFrame("Button", nil, mailDropdown)
charButton:SetWidth(188)
charButton:SetHeight(20)
charButton:SetPoint("TOP", mailDropdown, "TOP", 0, yOffset)
-- Button background on hover
local charBg = charButton:CreateTexture(nil, "BACKGROUND")
charBg:SetAllPoints()
charBg:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
charBg:SetBlendMode("ADD")
charBg:SetAlpha(0)
-- Get class color
local classColor = charClassToken and RAID_CLASS_COLORS[charClassToken]
local r, g, b = 1, 1, 1
if classColor then
r, g, b = classColor.r, classColor.g, classColor.b
end
-- Button text
local charText = charButton:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
charText:SetPoint("LEFT", charButton, "LEFT", 8, 0)
charText:SetText(charName)
charText:SetTextColor(r, g, b)
-- Button scripts
charButton:SetScript("OnEnter", function()
charBg:SetAlpha(0.3)
end)
charButton:SetScript("OnLeave", function()
charBg:SetAlpha(0)
end)
charButton:SetScript("OnClick", function()
if charFullName then
-- Show mailbox for this character
addon.Modules.MailboxFrame:ShowCharacter(charFullName)
if not Guda_MailboxFrame:IsShown() then
Guda_MailboxFrame:Show()
end
mailDropdown:Hide()
else
addon:Print("Error: Character fullName is nil")
end
end)
table.insert(mailDropdown.buttons, charButton)
yOffset = yOffset - 20
end
-- Set dropdown height based on content
mailDropdown:SetHeight(math.abs(yOffset) + 8)
-- Show dropdown
mailDropdown:Show()
end
-- Toggle bank dropdown -- Toggle bank dropdown
function Guda_BagFrame_ToggleBankDropdown(button) function Guda_BagFrame_ToggleBankDropdown(button)
-- Hide character dropdown if it's shown -- Hide character dropdown if it's shown
+46
View File
@@ -248,6 +248,52 @@
</Scripts> </Scripts>
</Button> </Button>
<!-- Mail Button (header left, next to bank) -->
<Button name="$parent_MailButton">
<Size>
<AbsDimension x="24" y="24"/>
</Size>
<Anchors>
<Anchor point="LEFT" relativePoint="RIGHT" relativeTo="$parent_BankButton">
<Offset>
<AbsDimension x="2" y="0"/>
</Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<Texture name="$parent_Icon">
<Size>
<AbsDimension x="20" y="20"/>
</Size>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
<Color r="0.8" g="0.8" b="0.8"/>
</Texture>
</Layer>
</Layers>
<Scripts>
<OnLoad>
local icon = getglobal(this:GetName().."_Icon")
if icon then
icon:SetTexture("Interface\\Icons\\INV_Letter_15")
end
</OnLoad>
<OnClick>
Guda_BagFrame_ToggleMailDropdown(this)
</OnClick>
<OnEnter>
GameTooltip:SetOwner(this, "ANCHOR_TOP")
GameTooltip:SetText("View Mailbox")
GameTooltip:Show()
</OnEnter>
<OnLeave>
GameTooltip:Hide()
</OnLeave>
</Scripts>
</Button>
<!-- Toolbar Container (for money tooltip and bag slots) --> <!-- Toolbar Container (for money tooltip and bag slots) -->
<Frame name="$parent_Toolbar" enableMouse="true"> <Frame name="$parent_Toolbar" enableMouse="true">
<Size> <Size>
+46
View File
@@ -107,6 +107,52 @@
</Scripts> </Scripts>
</Button> </Button>
<!-- Mail Button (header right, next to sort) -->
<Button name="$parent_MailButton">
<Size>
<AbsDimension x="24" y="24"/>
</Size>
<Anchors>
<Anchor point="RIGHT" relativePoint="LEFT" relativeTo="$parent_SortButton">
<Offset>
<AbsDimension x="-2" y="0"/>
</Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<Texture name="$parent_Icon">
<Size>
<AbsDimension x="20" y="20"/>
</Size>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
<Color r="0.8" g="0.8" b="0.8"/>
</Texture>
</Layer>
</Layers>
<Scripts>
<OnLoad>
local icon = getglobal(this:GetName().."_Icon")
if icon then
icon:SetTexture("Interface\\Icons\\INV_Letter_15")
end
</OnLoad>
<OnClick>
Guda_MailboxFrame:Toggle()
</OnClick>
<OnEnter>
GameTooltip:SetOwner(this, "ANCHOR_TOP")
GameTooltip:SetText("View Mailbox")
GameTooltip:Show()
</OnEnter>
<OnLeave>
GameTooltip:Hide()
</OnLeave>
</Scripts>
</Button>
<!-- Settings Button (header right, next to close) --> <!-- Settings Button (header right, next to close) -->
<Button name="$parent_SettingsButton"> <Button name="$parent_SettingsButton">
<Size> <Size>
+33 -5
View File
@@ -447,13 +447,18 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
self.bagID = bagID self.bagID = bagID
self.slotID = slotID self.slotID = slotID
-- Also set the Blizzard slot ID for compatibility with ContainerFrameItemButtonTemplate behavior -- Also set the Blizzard slot ID for compatibility with ContainerFrameItemButtonTemplate behavior
if self.SetID and slotID then -- ALWAYS set ID to something (0 if nil) to avoid leaking old IDs when button is reused
self:SetID(slotID) if self.SetID then
self:SetID(slotID or 0)
end end
-- Explicitly set bag index to avoid Blizzard's ContainerFrameItemButton_OnEnter logic
-- from picking up this button as part of a real bag.
self.bagIndex = bagID or -100 -- Use an invalid bag index for non-bag buttons
self.itemData = itemData self.itemData = itemData
self.isBank = isBank or false self.isBank = isBank or false
self.otherChar = otherCharName self.otherChar = otherCharName
self.isReadOnly = isReadOnly or false -- Track if this is read-only mode self.isReadOnly = isReadOnly or false -- Track if this is read-only mode
self.isMail = false -- Clear mailbox flag by default
-- Re-register for drag/drop every time (crucial for button reuse in Classic/Vanilla) -- Re-register for drag/drop every time (crucial for button reuse in Classic/Vanilla)
if not self.isReadOnly and not self.otherChar then if not self.isReadOnly and not self.otherChar then
@@ -538,7 +543,17 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
-- Apply the determined texture and count -- Apply the determined texture and count
if self.hasItem then if self.hasItem then
if SetItemButtonTexture then SetItemButtonTexture(self, displayTexture) end if SetItemButtonTexture then
SetItemButtonTexture(self, displayTexture)
end
-- Explicitly set and show icon texture as SetItemButtonTexture can be unreliable for custom paths in 1.12
local iconTexture = getglobal(self:GetName().."IconTexture") or getglobal(self:GetName().."Icon") or self.icon or self.Icon
if iconTexture and displayTexture then
iconTexture:SetTexture(displayTexture)
iconTexture:Show()
end
if SetItemButtonCount then SetItemButtonCount(self, displayCount or 1) end if SetItemButtonCount then SetItemButtonCount(self, displayCount or 1) end
if emptySlotBg then emptySlotBg:Hide() end if emptySlotBg then emptySlotBg:Hide() end
-- Update cooldown overlay for live items -- Update cooldown overlay for live items
@@ -625,6 +640,9 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
-- For 1.12.1 compatibility, access textures using both methods and proper naming -- For 1.12.1 compatibility, access textures using both methods and proper naming
-- We'll set the NormalTexture later based on whether slot is empty or filled -- We'll set the NormalTexture later based on whether slot is empty or filled
-- ICON TEXTURE: Ensure we use the correct name ($parentIconTexture is standard)
local iconTexture = getglobal(self:GetName().."IconTexture") or getglobal(self:GetName().."Icon") or self.icon or self.Icon
-- Pushed texture follows the button size/position -- Pushed texture follows the button size/position
local pushedTexture = getglobal(self:GetName().."PushedTexture") local pushedTexture = getglobal(self:GetName().."PushedTexture")
if not pushedTexture and self.GetPushedTexture then if not pushedTexture and self.GetPushedTexture then
@@ -920,8 +938,8 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
self.questIcon:ClearAllPoints() self.questIcon:ClearAllPoints()
self.questIcon:SetPoint("TOPRIGHT", self, "TOPRIGHT", 1, 0) self.questIcon:SetPoint("TOPRIGHT", self, "TOPRIGHT", 1, 0)
end end
else elseif not self.isMail then
-- Hide icon for empty slots -- Hide icon for empty slots, but keep it for mailbox custom icons
iconTexture:Hide() iconTexture:Hide()
end end
end end
@@ -929,6 +947,16 @@ end
-- OnEnter handler (show tooltip) -- OnEnter handler (show tooltip)
function Guda_ItemButton_OnEnter(self) function Guda_ItemButton_OnEnter(self)
-- DETACH from Blizzard's internal tooltip logic for mailbox items
if self.isMail or this.isMail or (this:GetParent() and this:GetParent():GetName() == "Guda_MailboxFrame_ItemContainer") then
return
end
-- Prevent Blizzard's ContainerFrameItemButton_OnEnter from running if it somehow got through
if not self.bagID or self.bagID == -100 then
return
end
-- Highlight the corresponding bag button in the footer (works for empty and filled slots) -- Highlight the corresponding bag button in the footer (works for empty and filled slots)
if not self.otherChar and self.bagID then if not self.otherChar and self.bagID then
if self.isBank then if self.isBank then
+302
View File
@@ -0,0 +1,302 @@
-- Mailbox Frame
-- Mailbox viewing UI
local addon = Guda
local MailboxFrame = {}
addon.Modules.MailboxFrame = MailboxFrame
local currentViewChar = nil
local searchText = ""
local isReadOnlyMode = true -- Mailbox is always read-only in this addon (viewing offline data)
local itemButtons = {}
local mailboxClickCatcher = nil
-- OnLoad
function Guda_MailboxFrame_OnLoad(self)
-- Set up initial backdrop
addon:ApplyBackdrop(self, "DEFAULT_FRAME")
-- Set up search box placeholder
local searchBox = getglobal(self:GetName().."_SearchBar_SearchBox")
if searchBox then
searchBox:SetText("Search mailbox...")
searchBox:SetTextColor(0.5, 0.5, 0.5, 1)
end
-- Create invisible full-screen frame to catch clicks outside the mailbox frame while typing in search
if not mailboxClickCatcher then
mailboxClickCatcher = CreateFrame("Frame", "Guda_MailboxClickCatcher", UIParent)
mailboxClickCatcher:SetFrameStrata("BACKGROUND")
mailboxClickCatcher:SetAllPoints(UIParent)
mailboxClickCatcher:EnableMouse(true)
mailboxClickCatcher:Hide()
mailboxClickCatcher:SetScript("OnMouseDown", function()
if Guda_MailboxFrame_ClearSearch then
Guda_MailboxFrame_ClearSearch()
end
end)
end
end
-- Clear search focus
function Guda_MailboxFrame_ClearSearch()
local searchBox = getglobal("Guda_MailboxFrame_SearchBar_SearchBox")
if searchBox then
searchBox:ClearFocus()
end
end
-- OnShow
function Guda_MailboxFrame_OnShow(self)
-- Apply frame transparency
if Guda_ApplyBackgroundTransparency then
Guda_ApplyBackgroundTransparency()
end
MailboxFrame:Update()
end
-- Toggle visibility
function MailboxFrame:Toggle()
if Guda_MailboxFrame:IsShown() then
Guda_MailboxFrame:Hide()
else
Guda_MailboxFrame:Show()
end
end
-- Show specific character's mailbox
function MailboxFrame:ShowCharacter(fullName)
currentViewChar = fullName
self:Update()
end
-- Initialize module
function MailboxFrame:Initialize()
-- Register events if needed
end
-- Update the mailbox frame
function MailboxFrame:Update()
if not Guda_MailboxFrame:IsShown() then return end
-- Determine which character to show
local charFullName = currentViewChar or addon.Modules.DB:GetPlayerFullName()
local mailboxData = addon.Modules.DB:GetCharacterMailbox(charFullName)
-- Extract character name from fullName
local charName = charFullName
local dashPos = string.find(charFullName, "-")
if dashPos then
charName = string.sub(charFullName, 1, dashPos - 1)
end
getglobal("Guda_MailboxFrame_Title"):SetText(charName .. "'s Mailbox")
-- Filter items based on search text
local filteredItems = {}
for i, mail in ipairs(mailboxData) do
local matchesSearch = true
if searchText ~= "" then
matchesSearch = false
if mail.sender and string.find(string.lower(mail.sender), searchText) then
matchesSearch = true
elseif mail.subject and string.find(string.lower(mail.subject), searchText) then
matchesSearch = true
elseif mail.item and mail.item.name and string.find(string.lower(mail.item.name), searchText) then
matchesSearch = true
end
end
if matchesSearch then
table.insert(filteredItems, mail)
end
end
-- Display items
self:DisplayItems(filteredItems, charFullName)
-- Update footer info
local totalItems = table.getn(mailboxData)
local displayedItems = table.getn(filteredItems)
local footerText = string.format("Items: %d", totalItems)
if searchText ~= "" then
footerText = string.format("Filtered: %d / %d", displayedItems, totalItems)
end
getglobal("Guda_MailboxFrame_Footer_Text"):SetText(footerText)
-- Update money (total money in mailbox)
local totalMoney = 0
for _, mail in ipairs(mailboxData) do
totalMoney = totalMoney + (mail.money or 0)
end
MoneyFrame_Update("Guda_MailboxFrame_MoneyFrame", totalMoney)
end
-- Display mailbox items in a grid
function MailboxFrame:DisplayItems(items, charFullName)
local container = getglobal("Guda_MailboxFrame_ItemContainer")
-- Explicitly set container ID to -100 to avoid being picked up as a bag
if container.SetID then container:SetID(-100) end
local columns = 10
local buttonSize = 34
local spacing = 2
-- Hide all existing buttons first
for _, button in pairs(itemButtons) do
button:Hide()
button.inUse = false
end
local row = 0
local col = 0
for i, mail in ipairs(items) do
local button = itemButtons[i]
if not button then
button = Guda_GetItemButton(container)
itemButtons[i] = button
end
-- Consistently set button size for the mailbox grid
button:SetWidth(buttonSize)
button:SetHeight(buttonSize)
button:ClearAllPoints()
button:SetPoint("TOPLEFT", container, "TOPLEFT", col * (buttonSize + spacing), -row * (buttonSize + spacing))
-- Set button data
button.isBank = false
button.otherChar = charFullName
button.isMail = true -- Custom flag for mailbox items
button.inUse = true
if mail.item and (mail.item.texture or mail.item.link) then
Guda_ItemButton_SetItem(button, nil, nil, mail.item, false, charFullName, true, true)
-- Re-enforce size because SetItem overrides it with global settings
button:SetWidth(buttonSize)
button:SetHeight(buttonSize)
button.isMail = true -- Re-apply after SetItem clears it
else
-- Use SetItem with nil itemData to clear the button properly first
Guda_ItemButton_SetItem(button, nil, nil, nil, false, charFullName, true, true)
button:SetWidth(buttonSize)
button:SetHeight(buttonSize)
button.isMail = true -- Re-apply after SetItem clears it
-- Then show our custom icon for money/mail
button.itemData = nil
local icon = getglobal(button:GetName().."IconTexture") or getglobal(button:GetName().."Icon")
if icon then
if (mail.money or 0) > 0 then
icon:SetTexture("Interface\\Icons\\INV_Misc_Coin_01")
elseif mail.packageIcon then
icon:SetTexture(mail.packageIcon)
else
icon:SetTexture("Interface\\Icons\\INV_Letter_15")
end
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
icon:Show()
end
if button.qualityBorder then button.qualityBorder:Hide() end
end
-- Custom Tooltip for mail items
button.mailData = mail -- Store data on button to avoid closure issues in Lua 5.0
button.isMail = true -- Redundant set to be safe
button:SetScript("OnEnter", function()
-- Force detachment from Blizzard logic inside the closure as well
if this.SetID then this:SetID(0) end
this.isMail = true
local mailData = this.mailData
if not mailData then return end
-- Explicitly use GameTooltip:ClearLines() and SetOwner to ensure a clean tooltip
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:ClearLines()
if mailData.item and mailData.item.link then
GameTooltip:SetHyperlink(mailData.item.link)
-- Add a gap if there's also sender/subject info to show
GameTooltip:AddLine(" ")
elseif mailData.item and mailData.item.name then
-- Fallback if we have item data but no link (e.g. not in cache yet)
GameTooltip:AddLine(mailData.item.name, 1, 1, 1)
GameTooltip:AddLine(" ")
end
GameTooltip:AddLine("From: " .. (mailData.sender or "Unknown"), 1, 1, 1)
GameTooltip:AddLine("Subject: " .. (mailData.subject or "No Subject"), 1, 1, 0.8)
if (mailData.money or 0) > 0 then
GameTooltip:AddLine("Money: " .. addon.Modules.Utils:FormatMoney(mailData.money), 1, 1, 1)
end
if (mailData.CODAmount or 0) > 0 then
GameTooltip:AddLine("COD: " .. addon.Modules.Utils:FormatMoney(mailData.CODAmount), 1, 0, 0)
end
if mailData.daysLeft then
GameTooltip:AddLine("Days left: " .. math.floor(mailData.daysLeft), 0.5, 0.5, 0.5)
end
GameTooltip:Show()
end)
button:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
-- Mailbox is read-only, so no clicking/dragging
button:RegisterForClicks()
button:SetScript("OnClick", nil)
button:SetScript("OnDragStart", nil)
button:SetScript("OnReceiveDrag", nil)
button:Show()
col = col + 1
if col >= columns then
col = 0
row = row + 1
end
end
-- Adjust container height
local totalRows = row + (col > 0 and 1 or 0)
container:SetHeight(math.max(420, totalRows * (buttonSize + spacing)))
end
-- Search text changed
function Guda_MailboxFrame_OnSearchTextChanged()
local searchBox = getglobal("Guda_MailboxFrame_SearchBar_SearchBox")
local text = searchBox:GetText()
if text == "Search mailbox..." then
searchText = ""
else
searchText = string.lower(text)
end
MailboxFrame:Update()
end
-- Show character selection menu
function Guda_MailboxFrame_ShowCharacterMenu()
local characters = addon.Modules.DB:GetAllCharacters(true, true)
local menu = {}
for i, char in ipairs(characters) do
local charFullName = char.fullName
table.insert(menu, {
text = char.name,
func = function() MailboxFrame:ShowCharacter(charFullName) end,
checked = (currentViewChar == char.fullName or (not currentViewChar and char.fullName == addon.Modules.DB:GetPlayerFullName()))
})
end
-- EasyMenu is available in 1.12.1
local menuFrame = CreateFrame("Frame", "Guda_MailboxCharacterMenu", UIParent, "UIDropDownMenuTemplate")
EasyMenu(menu, menuFrame, "cursor", 0, 0, "MENU")
end
+244
View File
@@ -0,0 +1,244 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/">
<!-- Mailbox Frame -->
<Frame name="Guda_MailboxFrame" toplevel="true" movable="true" enableMouse="true" hidden="true" parent="UIParent">
<Size>
<AbsDimension x="420" y="540"/>
</Size>
<Anchors>
<Anchor point="CENTER" relativePoint="CENTER" relativeTo="UIParent">
<Offset>
<AbsDimension x="0" y="0"/>
</Offset>
</Anchor>
</Anchors>
<Backdrop bgFile="Interface\ChatFrame\ChatFrameBackground" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
<BackgroundInsets>
<AbsInset left="11" right="12" top="12" bottom="11"/>
</BackgroundInsets>
<TileSize>
<AbsValue val="16"/>
</TileSize>
<EdgeSize>
<AbsValue val="32"/>
</EdgeSize>
</Backdrop>
<Layers>
<!-- Title at top -->
<Layer level="ARTWORK">
<FontString name="$parent_Title" inherits="GameFontNormalLarge">
<Anchors>
<Anchor point="TOP">
<Offset>
<AbsDimension x="0" y="-12"/>
</Offset>
</Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- Close Button -->
<Button name="$parent_CloseButton" inherits="UIPanelCloseButton">
<Anchors>
<Anchor point="TOPRIGHT">
<Offset>
<AbsDimension x="-16" y="-10"/>
</Offset>
</Anchor>
</Anchors>
<Scripts>
<OnClick>
Guda_MailboxFrame:Hide()
</OnClick>
</Scripts>
</Button>
<!-- Search Bar -->
<Frame name="$parent_SearchBar" inherits="OptionFrameBoxTemplate">
<Size>
<AbsDimension x="250" y="32"/>
</Size>
<Anchors>
<Anchor point="TOPLEFT">
<Offset>
<AbsDimension x="20" y="-35"/>
</Offset>
</Anchor>
</Anchors>
<Frames>
<EditBox name="$parent_SearchBox" autoFocus="false" inherits="InputBoxTemplate">
<Size>
<AbsDimension x="240" y="20"/>
</Size>
<Anchors>
<Anchor point="LEFT">
<Offset>
<AbsDimension x="5" y="0"/>
</Offset>
</Anchor>
</Anchors>
<Scripts>
<OnTextChanged>
Guda_MailboxFrame_OnSearchTextChanged()
</OnTextChanged>
<OnEscapePressed>
this:ClearFocus()
</OnEscapePressed>
<OnEnterPressed>
this:ClearFocus()
</OnEnterPressed>
<OnEditFocusGained>
if this:GetText() == "Search mailbox..." then
this:SetText("")
this:SetTextColor(1, 1, 1, 1)
end
if Guda_MailboxClickCatcher then
Guda_MailboxClickCatcher:Show()
end
</OnEditFocusGained>
<OnEditFocusLost>
if this:GetText() == "" then
this:SetText("Search mailbox...")
this:SetTextColor(0.5, 0.5, 0.5, 1)
end
if Guda_MailboxClickCatcher then
Guda_MailboxClickCatcher:Hide()
end
</OnEditFocusLost>
</Scripts>
</EditBox>
</Frames>
<Scripts>
<OnLoad>
this:SetBackdropBorderColor(0.4, 0.4, 0.4)
this:SetBackdropColor(0.15, 0.15, 0.15, 0.5)
</OnLoad>
</Scripts>
</Frame>
<!-- Character Selection Button -->
<Button name="$parent_CharacterButton">
<Size>
<AbsDimension x="24" y="24"/>
</Size>
<Anchors>
<Anchor point="TOPRIGHT">
<Offset>
<AbsDimension x="-20" y="-40"/>
</Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<Texture name="$parent_Icon">
<Size>
<AbsDimension x="20" y="20"/>
</Size>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
<Color r="0.8" g="0.8" b="0.8"/>
</Texture>
</Layer>
</Layers>
<Scripts>
<OnLoad>
local icon = getglobal(this:GetName().."_Icon")
if icon then
icon:SetTexture("Interface\\AddOns\\Guda\\Assets\\All_Characters")
end
</OnLoad>
<OnClick>
Guda_MailboxFrame_ShowCharacterMenu()
</OnClick>
<OnEnter>
GameTooltip:SetOwner(this, "ANCHOR_TOP")
GameTooltip:SetText("Switch Character")
GameTooltip:Show()
</OnEnter>
<OnLeave>
GameTooltip:Hide()
</OnLeave>
</Scripts>
</Button>
<!-- Scroll Frame for Items -->
<ScrollFrame name="$parent_ScrollFrame" inherits="UIPanelScrollFrameTemplate">
<Size>
<AbsDimension x="360" y="420"/>
</Size>
<Anchors>
<Anchor point="TOPLEFT">
<Offset>
<AbsDimension x="20" y="-75"/>
</Offset>
</Anchor>
</Anchors>
<ScrollChild>
<Frame name="Guda_MailboxFrame_ItemContainer">
<Size>
<AbsDimension x="360" y="420"/>
</Size>
<Anchors>
<Anchor point="TOPLEFT"/>
</Anchors>
</Frame>
</ScrollChild>
</ScrollFrame>
<!-- Footer for stats/info -->
<Frame name="$parent_Footer">
<Size>
<AbsDimension x="380" y="20"/>
</Size>
<Anchors>
<Anchor point="BOTTOMLEFT">
<Offset>
<AbsDimension x="20" y="15"/>
</Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<FontString name="$parent_Text" inherits="GameFontHighlightSmall" justifyH="LEFT">
<Anchors>
<Anchor point="LEFT"/>
</Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="$parent_MoneyFrame" inherits="SmallMoneyFrameTemplate">
<Anchors>
<Anchor point="BOTTOMRIGHT">
<Offset>
<AbsDimension x="-15" y="10"/>
</Offset>
</Anchor>
</Anchors>
</Frame>
</Frames>
<Scripts>
<OnLoad>
Guda_MailboxFrame_OnLoad(this)
</OnLoad>
<OnShow>
Guda_MailboxFrame_OnShow(this)
</OnShow>
<OnMouseDown>
this:StartMoving()
</OnMouseDown>
<OnMouseUp>
this:StopMovingOrSizing()
</OnMouseUp>
</Scripts>
</Frame>
</Ui>