-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNotificationManager.lua
More file actions
441 lines (390 loc) · 15.3 KB
/
Copy pathNotificationManager.lua
File metadata and controls
441 lines (390 loc) · 15.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
local addonName, ns = ...
ns.NotificationManager = {}
local NotificationManager = ns.NotificationManager
local anchorFrame = nil
local anchorUnlocked = false
local activeRows = {}
local rowPool = {}
local animDriverFrame = nil
local LAYOUT_TRANSITION_DURATION = 0.12
function NotificationManager.CalculateTravelY(baseOffset, progress, travelDistance, direction)
local directionMultiplier = direction == "DOWN" and -1 or 1
return baseOffset + progress * travelDistance * directionMultiplier
end
function NotificationManager.CalculateLayoutOffset(startOffset, targetOffset, progress)
local clampedProgress = math.max(0, math.min(1, tonumber(progress) or 0))
local easedProgress = clampedProgress * clampedProgress * (3 - 2 * clampedProgress)
return startOffset + (targetOffset - startOffset) * easedProgress
end
function NotificationManager.CalculateRowFrameLevel(baseFrameLevel, activeCount, rowIndex)
local baseLevel = tonumber(baseFrameLevel) or 0
local count = math.max(1, tonumber(activeCount) or 1)
local index = math.max(1, tonumber(rowIndex) or 1)
return baseLevel + math.max(1, count - index + 1)
end
function NotificationManager.CalculateLocationCounts(bagCount, totalCount, quantity)
local lootedQuantity = math.max(1, tonumber(quantity) or 1)
local bags = math.max(tonumber(bagCount) or 0, lootedQuantity)
local total = math.max(tonumber(totalCount) or 0, bags)
return bags, math.max(0, total - bags)
end
local function GetRowConfig()
return {
showIcons = ns.Database.Get("showIcons"),
showQuantity = ns.Database.Get("showQuantity"),
showOwnedCount = ns.Database.Get("showOwnedCount"),
showVendorValue = ns.Database.Get("showVendorValue"),
showBackground = ns.Database.Get("showBackground"),
backgroundOpacity = ns.Database.Get("backgroundOpacity"),
backgroundRounded = ns.Database.Get("backgroundRounded"),
rowOpacity = ns.Database.Get("rowOpacity"),
mouseInteraction = ns.Database.Get("mouseInteraction"),
iconSize = ns.Database.Get("iconSize") or 24,
fontSize = ns.Database.Get("fontSize") or 14,
maxWidth = ns.Database.Get("maxWidth") or 480,
scale = ns.Database.Get("scale") or 1.0,
}
end
local function UpdateOwnedCount(record, config)
if not record or record.kind ~= "item" then return end
if record.isPreview then return end
if not config.showOwnedCount then
record.bagCount = nil
record.bankCount = nil
return
end
local itemIdentifier = record.itemLink or record.itemID
local bagCount = ns.ApiCompat.GetItemCount(itemIdentifier, false)
local totalCount = ns.ApiCompat.GetItemCount(itemIdentifier, true)
if type(bagCount) == "number" and type(totalCount) == "number" then
record.bagCount, record.bankCount = NotificationManager.CalculateLocationCounts(
bagCount,
totalCount,
record.quantity
)
else
record.bagCount = nil
record.bankCount = nil
end
end
local function CreateAnchor()
if anchorFrame then return anchorFrame end
anchorFrame = CreateFrame("Frame", "SimpleScrollingLootAnchor", UIParent)
anchorFrame:SetSize(220, 30)
local savedAnchor = ns.Database.Get("anchor") or { point = "CENTER", relativePoint = "CENTER", x = 0, y = 120 }
anchorFrame:SetPoint(savedAnchor.point or "CENTER", UIParent, savedAnchor.relativePoint or "CENTER", savedAnchor.x or 0, savedAnchor.y or 120)
anchorFrame:SetMovable(true)
anchorFrame:EnableMouse(false)
anchorFrame:SetClampedToScreen(true)
-- Anchor visual backdrop when unlocked
local bg = anchorFrame:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(anchorFrame)
bg:SetColorTexture(0, 0.4, 0.8, 0.5)
bg:Hide()
anchorFrame.bg = bg
local title = anchorFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
title:SetPoint("CENTER", anchorFrame, "CENTER", 0, 0)
title:SetText(ns.L.ANCHOR_TITLE or "Simple Scrolling Loot")
title:Hide()
anchorFrame.title = title
anchorFrame:RegisterForDrag("LeftButton")
anchorFrame:SetScript("OnDragStart", function(self)
self:StartMoving()
end)
anchorFrame:SetScript("OnDragStop", function(self)
self:StopMovingOrSizing()
local point, _, relativePoint, x, y = self:GetPoint()
ns.Database.Set("anchor", {
point = point,
relativePoint = relativePoint,
x = math.floor(x + 0.5),
y = math.floor(y + 0.5),
})
end)
return anchorFrame
end
local function GetRowFromPool()
if #rowPool > 0 then
local row = table.remove(rowPool)
return row
end
return ns.NotificationRow.Create(CreateAnchor())
end
local function RecycleRow(row)
row:Reset()
table.insert(rowPool, row)
end
local function ApplySavedAnchor()
if not anchorFrame then return end
local savedAnchor = ns.Database.Get("anchor")
anchorFrame:ClearAllPoints()
anchorFrame:SetPoint(
savedAnchor.point,
UIParent,
savedAnchor.relativePoint,
savedAnchor.x,
savedAnchor.y
)
end
local function TrimVisibleRows()
local maxVisible = ns.Database.Get("maxVisible") or 6
while #activeRows > maxVisible do
local oldest = table.remove(activeRows)
RecycleRow(oldest.row)
end
end
function NotificationManager.Initialize()
CreateAnchor()
-- Animation driver frame
animDriverFrame = CreateFrame("Frame", "SimpleScrollingLootAnimDriver", UIParent)
animDriverFrame:SetScript("OnUpdate", function(self, elapsed)
NotificationManager.OnUpdate(elapsed)
end)
animDriverFrame:Hide()
local appearanceSettings = {
"showIcons",
"showQuantity",
"showOwnedCount",
"showVendorValue",
"showBackground",
"backgroundOpacity",
"backgroundRounded",
"rowOpacity",
"mouseInteraction",
"iconSize",
"fontSize",
"maxWidth",
"scale",
}
for _, key in ipairs(appearanceSettings) do
ns.Database.RegisterCallback(key, NotificationManager.RefreshActiveRows)
end
ns.Database.RegisterCallback("direction", NotificationManager.UpdateLayout)
ns.Database.RegisterCallback("staticMode", NotificationManager.UpdateLayout)
ns.Database.RegisterCallback("rowSpacing", NotificationManager.UpdateLayout)
ns.Database.RegisterCallback("duration", function(value)
for _, entry in ipairs(activeRows) do
entry.duration = value
end
end)
ns.Database.RegisterCallback("fadeDuration", function(value)
for _, entry in ipairs(activeRows) do
entry.fadeDuration = math.min(value, entry.duration)
end
end)
ns.Database.RegisterCallback("travelDistance", function(value)
for _, entry in ipairs(activeRows) do
entry.travelDistance = value
end
end)
ns.Database.RegisterCallback("maxVisible", function()
TrimVisibleRows()
NotificationManager.UpdateLayout()
end)
ns.Database.RegisterCallback("anchor", ApplySavedAnchor)
ns.Database.RegisterCallback("enabled", function(enabled)
if not enabled then
NotificationManager.Clear()
end
end)
end
function NotificationManager.AddNotification(record)
if not ns.Database.Get("enabled") then return end
if not record then return end
-- Filter checks
if record.kind == "item" then
if not ns.Database.Get("showItems") then return end
local minQuality = ns.Database.Get("minQuality") or 0
if (record.quality or 1) < minQuality then
ns.Debug.Log("Item %s filtered out by minQuality (%d < %d)", tostring(record.name), record.quality or 1, minQuality)
return
end
elseif record.kind == "money" then
if not ns.Database.Get("showMoney") then return end
end
CreateAnchor()
local config = GetRowConfig()
UpdateOwnedCount(record, config)
local row = GetRowFromPool()
row:SetScale(config.scale)
row:SetRecord(record, config)
local spawnTime = GetTime()
local entry = {
row = row,
spawnTime = spawnTime,
duration = ns.Database.Get("duration") or 4.0,
fadeDuration = ns.Database.Get("fadeDuration") or 0.8,
travelDistance = ns.Database.Get("travelDistance") or 90,
opacity = config.rowOpacity or 1.0,
layoutOffset = 0,
layoutStartOffset = 0,
layoutTargetOffset = 0,
layoutStartTime = spawnTime,
}
table.insert(activeRows, 1, entry)
-- Limit visible rows
TrimVisibleRows()
NotificationManager.UpdateLayout()
row:ClearAllPoints()
row:SetPoint("CENTER", anchorFrame, "CENTER", 0, 0)
row:SetAlpha(entry.opacity)
row:Show()
animDriverFrame:Show()
end
function NotificationManager.RefreshActiveRows()
local config = GetRowConfig()
for _, entry in ipairs(activeRows) do
UpdateOwnedCount(entry.row.record, config)
entry.row:SetScale(config.scale)
entry.row:SetRecord(entry.row.record, config)
entry.opacity = config.rowOpacity or 1.0
end
NotificationManager.UpdateLayout()
end
function NotificationManager.RefreshOwnedCounts()
if not ns.Database.Get("showOwnedCount") or #activeRows == 0 then return end
NotificationManager.RefreshActiveRows()
end
function NotificationManager.UpdateLayout()
local direction = ns.Database.Get("direction") or "UP"
local rowSpacing = ns.Database.Get("rowSpacing") or 4
local dirMultiplier = (direction == "UP") and 1 or -1
local now = GetTime()
local baseFrameLevel = anchorFrame:GetFrameLevel()
local currentOffset = 0
for index, entry in ipairs(activeRows) do
local row = entry.row
local rowHeight = row:GetHeight() * row:GetScale()
local transitionProgress = (now - (entry.layoutStartTime or now)) / LAYOUT_TRANSITION_DURATION
local visualOffset = NotificationManager.CalculateLayoutOffset(
entry.layoutStartOffset or entry.layoutOffset or 0,
entry.layoutTargetOffset or entry.layoutOffset or 0,
transitionProgress
)
entry.layoutOffset = visualOffset
entry.layoutStartOffset = visualOffset
entry.layoutTargetOffset = currentOffset * dirMultiplier
entry.layoutStartTime = now
row:SetFrameLevel(NotificationManager.CalculateRowFrameLevel(
baseFrameLevel,
#activeRows,
index
))
currentOffset = currentOffset + rowHeight + rowSpacing
end
end
function NotificationManager.OnUpdate(elapsed)
if #activeRows == 0 then
if animDriverFrame then animDriverFrame:Hide() end
return
end
local now = GetTime()
local staticMode = ns.Database.Get("staticMode") or false
local needsLayoutUpdate = false
local i = 1
while i <= #activeRows do
local entry = activeRows[i]
local age = now - entry.spawnTime
local totalDuration = entry.duration
local fadeDuration = entry.fadeDuration
if age >= totalDuration then
RecycleRow(entry.row)
table.remove(activeRows, i)
needsLayoutUpdate = true
-- Do not increment i; the next entry slides into index i.
else
local layoutProgress = (now - entry.layoutStartTime) / LAYOUT_TRANSITION_DURATION
entry.layoutOffset = NotificationManager.CalculateLayoutOffset(
entry.layoutStartOffset,
entry.layoutTargetOffset,
layoutProgress
)
-- Fade: full opacity for most of the lifetime, fade at the end.
local remaining = totalDuration - age
local alpha = (remaining < fadeDuration)
and math.max(0.0, remaining / fadeDuration)
or 1.0
entry.row:SetAlpha(alpha * entry.opacity)
-- Smooth linear travel: interpolate from baseOffset to baseOffset+travelDistance.
-- baseOffset is set by UpdateLayout() and does not change here.
if not staticMode then
local progress = math.min(1.0, age / totalDuration)
local direction = ns.Database.Get("direction") or "UP"
local animY = NotificationManager.CalculateTravelY(
entry.layoutOffset,
progress,
entry.travelDistance,
direction
)
entry.row:ClearAllPoints()
entry.row:SetPoint("CENTER", anchorFrame, "CENTER", 0, animY)
else
entry.row:ClearAllPoints()
entry.row:SetPoint("CENTER", anchorFrame, "CENTER", 0, entry.layoutOffset)
end
i = i + 1
end
end
-- Rebuild layout once after removing expired rows, not inside the loop.
if needsLayoutUpdate then
NotificationManager.UpdateLayout()
end
if #activeRows == 0 and animDriverFrame then
animDriverFrame:Hide()
end
end
function NotificationManager.Clear()
for _, entry in ipairs(activeRows) do
RecycleRow(entry.row)
end
activeRows = {}
if animDriverFrame then
animDriverFrame:Hide()
end
end
function NotificationManager.ResetAnchor()
ns.Database.Set("anchor", {
point = ns.Defaults.anchor.point,
relativePoint = ns.Defaults.anchor.relativePoint,
x = ns.Defaults.anchor.x,
y = ns.Defaults.anchor.y,
})
end
function NotificationManager.UnlockAnchor()
local anchor = CreateAnchor()
anchorUnlocked = true
anchor:EnableMouse(true)
anchor.bg:Show()
anchor.title:Show()
ns.NotificationManager.ShowTestNotifications()
if ns.Options and type(ns.Options.RefreshPositionControl) == "function" then
ns.Options.RefreshPositionControl()
end
end
function NotificationManager.LockAnchor()
local anchor = CreateAnchor()
anchorUnlocked = false
anchor:EnableMouse(false)
anchor.bg:Hide()
anchor.title:Hide()
if ns.Options and type(ns.Options.RefreshPositionControl) == "function" then
ns.Options.RefreshPositionControl()
end
end
function NotificationManager.IsAnchorUnlocked()
return anchorUnlocked
end
function NotificationManager.ShowTestNotifications()
local testItems = {
{ kind = "item", itemID = 14047, name = ns.L.TEST_ITEM_1 or "Runecloth", itemLink = "|cffffffff|Hitem:14047:0:0:0:0:0:0:0|h[Runecloth]|h|r", quality = 1, quantity = 5, bagCount = 28, bankCount = 20, isPreview = true, sellPrice = 250 },
{ kind = "item", itemID = 4234, name = ns.L.TEST_ITEM_2 or "Heavy Leather", itemLink = "|cffffffff|Hitem:4234:0:0:0:0:0:0:0|h[Heavy Leather]|h|r", quality = 1, quantity = 2, bagCount = 17, bankCount = 0, isPreview = true, sellPrice = 150 },
{ kind = "item", itemID = 12360, name = ns.L.TEST_ITEM_3 or "Arcanite Bar", itemLink = "|cffa335ee|Hitem:12360:0:0:0:0:0:0:0|h[Arcanite Bar]|h|r", quality = 4, quantity = 1, bagCount = 1, bankCount = 3, isPreview = true, sellPrice = 50000 },
{ kind = "money", copper = 2580, formattedText = ns.ApiCompat.FormatMoney(2580), coinIconsText = ns.ApiCompat.GetCoinIconsText(2580) },
}
for _, rec in ipairs(testItems) do
if rec.itemID then
rec.texture = ns.ApiCompat.GetItemIcon(rec.itemID) or "Interface\\Icons\\INV_Misc_QuestionMark"
end
NotificationManager.AddNotification(rec)
end
end