mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-22 07:36:56 +00:00
863d99647d
Both modules had already grown a pool by hand. loothistory kept usedPlayers / freePlayers tables with a recycle loop and a table.remove acquire; mapreveal kept pfOverlays keyed by index, a pfOverlayMax high-water mark, and a hide-the-tail loop. Replace both with ClassicAPI's Pools.lua backport, which is what modules/tooltip.lua already uses for its buff icons. The map explore icons change shape rather than just plumbing. Everything constant -- size, scripts, mouse, frame level, the magnifying glass texture -- moves into the pool's creator instead of being re-set on every overlay on every map update, and the acquire moves inside the visibility test. The old loop built and configured an icon for every overlay in the zone and then hid most of them again; with "mapexploration" off it built the whole set and hid all of them. Now nothing is acquired for an icon that will not be shown. The tile textures lose their pfRevealN names, since pools create anonymous regions. They were already unique, so nothing was being clobbered -- this only costs their labels in /fstack.
372 lines
14 KiB
Lua
372 lines
14 KiB
Lua
pfUI:RegisterModule("loothistory", function ()
|
|
local rawborder, border = GetBorderSize()
|
|
|
|
-- Layout
|
|
local ITEM_H, PLAYER_H = 24, 18
|
|
local ITEM_W, PLAYER_W = 350, 330
|
|
|
|
-- rollType constants returned by C_LootHistory.GetPlayerInfo (0/1/2; vanilla
|
|
-- has no disenchant roll).
|
|
local ROLL_PASS, ROLL_NEED, ROLL_GREED = 0, 1, 2
|
|
local ROLL_TEX = {
|
|
[ROLL_NEED] = "Interface\\Buttons\\UI-GroupLoot-Dice-Up",
|
|
[ROLL_GREED] = "Interface\\Buttons\\UI-GroupLoot-Coin-Up",
|
|
[ROLL_PASS] = "Interface\\Buttons\\UI-GroupLoot-Pass-Up",
|
|
}
|
|
local WINMARK = "Interface\\Buttons\\UI-CheckBox-Check"
|
|
local QUESTIONMARK = "Interface\\Icons\\INV_Misc_QuestionMark"
|
|
|
|
-- Paint an item row's icon/name/quality from a loaded Item mixin.
|
|
local function RenderItemVisual(f, item)
|
|
local r, g, b = 1, 1, 1
|
|
local qc = item:GetItemQualityColor()
|
|
if qc then r, g, b = qc.r, qc.g, qc.b end
|
|
f.icon:SetTexture(item:GetItemIcon())
|
|
f.iconbg:SetBackdropBorderColor(r, g, b, 1)
|
|
f.name:SetText(item:GetItemName() or UNKNOWN)
|
|
f.name:SetTextColor(r, g, b)
|
|
end
|
|
|
|
local function ShowRetrieving(f)
|
|
f.icon:SetTexture(QUESTIONMARK)
|
|
f.iconbg:SetBackdropBorderColor(1, .3, .3, 1)
|
|
f.name:SetText(T["Retrieving item information..."])
|
|
f.name:SetTextColor(1, .3, .3)
|
|
end
|
|
|
|
-- expansion state keyed on the stable rollID (survives ring-index shifts)
|
|
local expanded = {}
|
|
|
|
-- ==========================================================================
|
|
-- Window
|
|
-- ==========================================================================
|
|
pfUI.loothistory = CreateFrame("Frame", "pfLootHistory", UIParent)
|
|
pfUI.loothistory:SetFrameStrata("DIALOG")
|
|
pfUI.loothistory:SetSize(380, 490)
|
|
pfUI.loothistory:SetPoint("CENTER", 0, 0)
|
|
pfUI.loothistory:SetMovable(true)
|
|
pfUI.loothistory:EnableMouse(true)
|
|
pfUI.loothistory:RegisterForDrag("LeftButton")
|
|
pfUI.loothistory:SetScript("OnDragStart", function() this:StartMoving() end)
|
|
pfUI.loothistory:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
|
pfUI.loothistory:Hide()
|
|
|
|
CreateBackdrop(pfUI.loothistory, nil, true, .75)
|
|
CreateBackdropShadow(pfUI.loothistory)
|
|
tinsert(UISpecialFrames, "pfLootHistory")
|
|
|
|
pfUI.loothistory.caption = pfUI.loothistory:CreateFontString("Status", "LOW", "GameFontNormal")
|
|
pfUI.loothistory.caption:SetFont(pfUI.font_default, C.global.font_size + 4, "OUTLINE")
|
|
pfUI.loothistory.caption:SetTextColor(.2, 1, .8, 1)
|
|
pfUI.loothistory.caption:SetPoint("TOP", 0, -10)
|
|
pfUI.loothistory.caption:SetText(T["Loot History"])
|
|
|
|
-- close button
|
|
pfUI.loothistory.close = CreateFrame("Button", nil, pfUI.loothistory)
|
|
pfUI.loothistory.close:SetPoint("TOPRIGHT", -border*2, -border*2)
|
|
CreateBackdrop(pfUI.loothistory.close)
|
|
pfUI.loothistory.close:SetSize(15, 15)
|
|
pfUI.loothistory.close.texture = pfUI.loothistory.close:CreateTexture("pfLootHistoryClose")
|
|
pfUI.loothistory.close.texture:SetTexture(pfUI.media["img:close"])
|
|
pfUI.loothistory.close.texture:SetPoint("TOPLEFT", pfUI.loothistory.close, "TOPLEFT", 4, -4)
|
|
pfUI.loothistory.close.texture:SetPoint("BOTTOMRIGHT", pfUI.loothistory.close, "BOTTOMRIGHT", -4, 4)
|
|
pfUI.loothistory.close.texture:SetVertexColor(1, .25, .25, 1)
|
|
pfUI.loothistory.close:SetScript("OnEnter", function()
|
|
CreateBackdrop(pfUI.loothistory.close)
|
|
pfUI.loothistory.close.backdrop:SetBackdropBorderColor(1, .25, .25, 1)
|
|
end)
|
|
pfUI.loothistory.close:SetScript("OnLeave", function() CreateBackdrop(pfUI.loothistory.close) end)
|
|
pfUI.loothistory.close:SetScript("OnClick", function() pfUI.loothistory:Hide() end)
|
|
|
|
-- clear button
|
|
pfUI.loothistory.clear = CreateFrame("Button", nil, pfUI.loothistory, "UIPanelButtonTemplate")
|
|
SkinButton(pfUI.loothistory.clear)
|
|
pfUI.loothistory.clear:SetSize(60, 16)
|
|
pfUI.loothistory.clear:SetPoint("TOPLEFT", 10, -8)
|
|
pfUI.loothistory.clear:SetText(T["Clear"])
|
|
pfUI.loothistory.clear:SetScript("OnClick", function() C_LootHistory.Clear() end)
|
|
|
|
-- scroll frame
|
|
pfUI.loothistory.scroll = CreateScrollFrame("pfLootHistoryScroll", pfUI.loothistory)
|
|
pfUI.loothistory.scroll:SetSize(360, 440)
|
|
pfUI.loothistory.scroll:SetPoint("BOTTOM", 0, 10)
|
|
|
|
pfUI.loothistory.scroll.backdrop = CreateFrame("Frame", nil, pfUI.loothistory.scroll)
|
|
pfUI.loothistory.scroll.backdrop:SetFrameLevel(1)
|
|
pfUI.loothistory.scroll.backdrop:SetPoint("TOPLEFT", pfUI.loothistory.scroll, "TOPLEFT", -5, 5)
|
|
pfUI.loothistory.scroll.backdrop:SetPoint("BOTTOMRIGHT", pfUI.loothistory.scroll, "BOTTOMRIGHT", 5, -5)
|
|
CreateBackdrop(pfUI.loothistory.scroll.backdrop, nil, true)
|
|
|
|
local list = CreateScrollChild("pfLootHistoryList", pfUI.loothistory.scroll)
|
|
pfUI.loothistory.list = list
|
|
|
|
-- ==========================================================================
|
|
-- Frame pools
|
|
-- ==========================================================================
|
|
local itemFrames = {}
|
|
|
|
local FullUpdate -- forward declaration (toggle handlers call it)
|
|
|
|
local function CreateItemFrame()
|
|
local f = CreateFrame("Button", nil, list)
|
|
f:SetSize(ITEM_W, ITEM_H)
|
|
f:SetBackdrop(pfUI.backdrop_hover)
|
|
f:SetBackdropBorderColor(1, 1, 1, .04)
|
|
f:EnableMouse(1)
|
|
|
|
-- expand / collapse toggle
|
|
f.toggle = CreateFrame("Button", nil, f)
|
|
f.toggle:SetSize(14, 14)
|
|
f.toggle:SetPoint("LEFT", 4, 0)
|
|
f.toggle:SetScript("OnClick", function()
|
|
local id = f.rollID
|
|
if id then expanded[id] = not expanded[id]; FullUpdate() end
|
|
end)
|
|
|
|
-- icon + quality-colored border
|
|
f.iconbg = CreateFrame("Frame", nil, f)
|
|
f.iconbg:SetSize(ITEM_H - 8, ITEM_H - 8)
|
|
f.iconbg:SetPoint("LEFT", f.toggle, "RIGHT", 4, 0)
|
|
CreateBackdrop(f.iconbg, nil, true)
|
|
f.icon = f.iconbg:CreateTexture(nil, "ARTWORK")
|
|
f.icon:SetPoint("TOPLEFT", f.iconbg, "TOPLEFT", 2, -2)
|
|
f.icon:SetPoint("BOTTOMRIGHT", f.iconbg, "BOTTOMRIGHT", -2, 2)
|
|
f.icon:SetTexCoord(.08, .92, .08, .92)
|
|
|
|
-- winner block (right side, shown for decided rolls)
|
|
f.winicon = f:CreateTexture(nil, "OVERLAY")
|
|
f.winicon:SetSize(14, 14)
|
|
f.winicon:SetPoint("RIGHT", f, "RIGHT", -6, 0)
|
|
|
|
f.winroll = f:CreateFontString("Status", "OVERLAY", "GameFontNormal")
|
|
f.winroll:SetFont(pfUI.font_default, C.global.font_size, "OUTLINE")
|
|
f.winroll:SetPoint("RIGHT", f.winicon, "LEFT", -2, 0)
|
|
f.winroll:SetTextColor(1, 1, 1, 1)
|
|
|
|
f.winname = f:CreateFontString("Status", "OVERLAY", "GameFontNormal")
|
|
f.winname:SetFont(pfUI.font_default, C.global.font_size, "OUTLINE")
|
|
f.winname:SetPoint("RIGHT", f.winroll, "LEFT", -4, 0)
|
|
f.winname:SetJustifyH("RIGHT")
|
|
|
|
-- item name (leaves room on the right for the winner block)
|
|
f.name = f:CreateFontString("Status", "OVERLAY", "GameFontNormal")
|
|
f.name:SetFont(pfUI.font_default, C.global.font_size, "OUTLINE")
|
|
f.name:SetPoint("LEFT", f.iconbg, "RIGHT", 5, 0)
|
|
f.name:SetPoint("RIGHT", f, "RIGHT", -100, 0)
|
|
f.name:SetJustifyH("LEFT")
|
|
|
|
f:SetScript("OnEnter", function()
|
|
this:SetBackdropBorderColor(1, 1, 1, .08)
|
|
if this.itemLink then
|
|
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
|
GameTooltip:SetHyperlink(this.itemLink)
|
|
GameTooltip:Show()
|
|
end
|
|
end)
|
|
f:SetScript("OnLeave", function()
|
|
this:SetBackdropBorderColor(1, 1, 1, .04)
|
|
GameTooltip:Hide()
|
|
end)
|
|
f:SetScript("OnClick", function()
|
|
local id = this.rollID
|
|
if id then expanded[id] = not expanded[id]; FullUpdate() end
|
|
end)
|
|
|
|
return f
|
|
end
|
|
|
|
local function CreatePlayerFrame()
|
|
local f = CreateFrame("Frame", nil, list)
|
|
f:SetSize(PLAYER_W, PLAYER_H)
|
|
|
|
-- name is indented to leave room for the winner checkmark on its left
|
|
f.name = f:CreateFontString("Status", "OVERLAY", "GameFontNormal")
|
|
f.name:SetFont(pfUI.font_default, C.global.font_size, "OUTLINE")
|
|
f.name:SetPoint("LEFT", 20, 0)
|
|
f.name:SetJustifyH("LEFT")
|
|
|
|
f.rollicon = f:CreateTexture(nil, "OVERLAY")
|
|
f.rollicon:SetSize(16, 16)
|
|
f.rollicon:SetPoint("RIGHT", -4, 0)
|
|
|
|
f.rolltext = f:CreateFontString("Status", "OVERLAY", "GameFontNormal")
|
|
f.rolltext:SetFont(pfUI.font_default, C.global.font_size, "OUTLINE")
|
|
f.rolltext:SetPoint("RIGHT", f.rollicon, "LEFT", -3, 0)
|
|
f.rolltext:SetTextColor(1, 1, 1, 1)
|
|
|
|
-- winner checkmark, just left of the player name (matches reference)
|
|
f.winmark = f:CreateTexture(nil, "OVERLAY")
|
|
f.winmark:SetSize(16, 16)
|
|
f.winmark:SetTexture(WINMARK)
|
|
f.winmark:SetPoint("RIGHT", f.name, "LEFT", -1, 0)
|
|
|
|
return f
|
|
end
|
|
|
|
local playerPool = CreateObjectPool(CreatePlayerFrame, function(_, pf)
|
|
pf:Hide()
|
|
pf:ClearAllPoints()
|
|
end)
|
|
|
|
local function SetToggleTexture(toggle, isExpanded)
|
|
if isExpanded then
|
|
toggle:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-Up")
|
|
toggle:SetPushedTexture("Interface\\Buttons\\UI-MinusButton-Down")
|
|
else
|
|
toggle:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-Up")
|
|
toggle:SetPushedTexture("Interface\\Buttons\\UI-PlusButton-Down")
|
|
end
|
|
end
|
|
|
|
-- ==========================================================================
|
|
-- Rendering
|
|
-- ==========================================================================
|
|
local function UpdateItemFrame(f, i)
|
|
local rollID, itemLink, numPlayers, isDone, winnerIdx = C_LootHistory.GetItem(i)
|
|
f.rollID = rollID
|
|
f.itemIdx = i
|
|
f.itemLink = itemLink
|
|
f.numPlayers = numPlayers or 0
|
|
f.isDone = isDone
|
|
|
|
local isExpanded = rollID and expanded[rollID]
|
|
SetToggleTexture(f.toggle, isExpanded)
|
|
|
|
-- Item icon/name/quality via the ClassicAPI Item mixin. When the item
|
|
-- isn't cached yet, show a placeholder and re-paint this row from the
|
|
-- ContinueOnItemLoad callback (guarded on rollID: rows are pooled, so the
|
|
-- callback must no-op if the row has since been reused for another roll).
|
|
local item = itemLink and Item:CreateFromItemLink(itemLink)
|
|
if item and not item:IsItemEmpty() then
|
|
if item:IsItemDataCached() then
|
|
RenderItemVisual(f, item)
|
|
else
|
|
ShowRetrieving(f)
|
|
local pending = rollID
|
|
item:ContinueOnItemLoad(function()
|
|
if f.rollID == pending then RenderItemVisual(f, item) end
|
|
end)
|
|
end
|
|
else
|
|
ShowRetrieving(f)
|
|
end
|
|
|
|
-- winner summary only on a decided, collapsed row
|
|
if isDone and not isExpanded then
|
|
if winnerIdx then
|
|
local wname, wclass, wrollType, wroll = C_LootHistory.GetPlayerInfo(i, winnerIdx)
|
|
f.winicon:SetTexture(ROLL_TEX[wrollType] or ROLL_TEX[ROLL_NEED])
|
|
f.winicon:Show()
|
|
if wroll and wroll > 0 then f.winroll:SetText(wroll) else f.winroll:SetText("") end
|
|
f.winroll:Show()
|
|
f.winname:SetText(wname or UNKNOWN)
|
|
f.winname:SetTextColor(PFUI_CLASS_COLORS[wclass]:GetRGB())
|
|
f.winname:Show()
|
|
else
|
|
-- nobody won: everyone passed
|
|
f.winicon:SetTexture(ROLL_TEX[ROLL_PASS])
|
|
f.winicon:Show()
|
|
f.winroll:SetText("")
|
|
f.winroll:Show()
|
|
f.winname:SetText(T["All players passed"])
|
|
f.winname:SetTextColor(1, .4, .4)
|
|
f.winname:Show()
|
|
end
|
|
else
|
|
f.winicon:Hide()
|
|
f.winroll:Hide()
|
|
f.winname:Hide()
|
|
end
|
|
end
|
|
|
|
local function RenderPlayerFrame(pf, name, class, rollType, roll, isWinner)
|
|
pf.name:SetText(name or UNKNOWN)
|
|
pf.name:SetTextColor(PFUI_CLASS_COLORS[class]:GetRGB())
|
|
|
|
pf.rollicon:SetTexture(ROLL_TEX[rollType] or ROLL_TEX[ROLL_PASS])
|
|
if roll and roll > 0 then pf.rolltext:SetText(roll) else pf.rolltext:SetText("") end
|
|
|
|
if isWinner then pf.winmark:Show() else pf.winmark:Hide() end
|
|
end
|
|
|
|
-- A player row is worth showing while the roll is undecided (see everyone),
|
|
-- or afterwards only if they actually rolled or it's you (hide the passers'
|
|
-- noise on a decided roll) — mirrors Blizzard's ShouldDisplayPlayer.
|
|
local function ShouldDisplayPlayer(isDone, roll, isMe)
|
|
return isMe or (roll and roll > 0) or not isDone
|
|
end
|
|
|
|
function FullUpdate()
|
|
if not pfUI.loothistory:IsShown() then return end
|
|
playerPool:ReleaseAll()
|
|
|
|
local num = C_LootHistory.GetNumItems()
|
|
local y = -2
|
|
|
|
for i = 1, num do
|
|
local f = itemFrames[i] or CreateItemFrame()
|
|
itemFrames[i] = f
|
|
UpdateItemFrame(f, i)
|
|
f:ClearAllPoints()
|
|
f:SetPoint("TOPLEFT", list, "TOPLEFT", 4, y)
|
|
f:Show()
|
|
y = y - ITEM_H - 2
|
|
|
|
if f.rollID and expanded[f.rollID] then
|
|
for p = 1, f.numPlayers do
|
|
local name, class, rollType, roll, isWinner, isMe = C_LootHistory.GetPlayerInfo(i, p)
|
|
if ShouldDisplayPlayer(f.isDone, roll, isMe) then
|
|
local pf = playerPool:Acquire()
|
|
RenderPlayerFrame(pf, name, class, rollType, roll, isWinner)
|
|
pf:ClearAllPoints()
|
|
pf:SetPoint("TOPLEFT", list, "TOPLEFT", 22, y)
|
|
pf:Show()
|
|
y = y - PLAYER_H
|
|
end
|
|
end
|
|
y = y - 2
|
|
end
|
|
end
|
|
|
|
for i = num + 1, table.getn(itemFrames) do
|
|
itemFrames[i]:Hide()
|
|
end
|
|
|
|
-- Resize the scroll child and re-attach it: SetHeight alone on a
|
|
-- SetAllPoints'd child doesn't make the ScrollFrame recompute its scroll
|
|
-- range, so a dynamically-grown list wouldn't scroll until a /reload.
|
|
list:SetHeight(math.max(1, -y + 2))
|
|
pfUI.loothistory.scroll:SetScrollChild(list)
|
|
pfUI.loothistory.scroll:UpdateScrollState()
|
|
end
|
|
|
|
pfUI.loothistory:SetScript("OnShow", function() FullUpdate() end)
|
|
|
|
-- ==========================================================================
|
|
-- Events
|
|
-- ==========================================================================
|
|
local events = CreateFrame("Frame")
|
|
events:RegisterEvent("LOOT_HISTORY_FULL_UPDATE")
|
|
events:RegisterEvent("LOOT_HISTORY_ROLL_CHANGED")
|
|
events:RegisterEvent("LOOT_HISTORY_ROLL_COMPLETE")
|
|
events:SetScript("OnEvent", function()
|
|
-- auto-show on a new roll opening / completing, if enabled
|
|
if C.loothistory.autoshow == "1"
|
|
and (event == "LOOT_HISTORY_FULL_UPDATE" or event == "LOOT_HISTORY_ROLL_COMPLETE")
|
|
and not pfUI.loothistory:IsShown() then
|
|
pfUI.loothistory:Show() -- OnShow runs FullUpdate
|
|
return
|
|
end
|
|
FullUpdate() -- no-op while hidden
|
|
end)
|
|
|
|
-- ==========================================================================
|
|
-- Slash command
|
|
-- ==========================================================================
|
|
local function Toggle()
|
|
pfUI.loothistory:SetShown(not pfUI.loothistory:IsShown())
|
|
end
|
|
|
|
pfUI.api.RegisterSlashCommand("PFLOOTHISTORY", { "/loothistory", "/pfloothistory" }, Toggle, true)
|
|
end)
|