You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
907 lines
29 KiB
907 lines
29 KiB
-----------------------------------------------------------------------
|
|
-- Upvalued Lua API.
|
|
-----------------------------------------------------------------------
|
|
local _G = getfenv(0)
|
|
|
|
-- Functions
|
|
local error = _G.error
|
|
local pairs = _G.pairs
|
|
|
|
-- Libraries
|
|
local table = _G.table
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Library namespace.
|
|
-----------------------------------------------------------------------
|
|
local LibStub = _G.LibStub
|
|
local MAJOR = "LibToast-1.0"
|
|
|
|
_G.assert(LibStub, MAJOR .. " requires LibStub")
|
|
|
|
local MINOR = 15 -- Should be manually increased
|
|
local LibToast, previousMinorVersion = LibStub:NewLibrary(MAJOR, MINOR)
|
|
|
|
if not LibToast then
|
|
return
|
|
end -- No upgrade needed
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Migrations.
|
|
-----------------------------------------------------------------------
|
|
LibToast.active_toasts = LibToast.active_toasts or {}
|
|
LibToast.queued_toasts = LibToast.queued_toasts or {}
|
|
LibToast.templates = LibToast.templates or {}
|
|
LibToast.unique_templates = LibToast.unique_templates or {}
|
|
|
|
LibToast.button_heap = LibToast.button_heap or {}
|
|
LibToast.toast_heap = LibToast.toast_heap or {}
|
|
|
|
LibToast.addon_names = LibToast.addon_names or {}
|
|
LibToast.registered_sink = LibToast.registered_sink
|
|
LibToast.sink_icons = LibToast.sink_icons or {}
|
|
LibToast.sink_template = LibToast.sink_template or {} -- Cheating here, since users can only use strings.
|
|
LibToast.sink_titles = LibToast.sink_titles or {}
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Variables.
|
|
-----------------------------------------------------------------------
|
|
local CurrentToast
|
|
local IsInternalCall
|
|
local CallingObject
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Constants.
|
|
-----------------------------------------------------------------------
|
|
local ActiveToasts = LibToast.active_toasts
|
|
local QueuedToasts = LibToast.queued_toasts
|
|
local ToastHeap = LibToast.toast_heap
|
|
local ButtonHeap = LibToast.button_heap
|
|
|
|
local ToastProxy = {}
|
|
|
|
local METHOD_USAGE_FORMAT = MAJOR .. ":%s() - %s."
|
|
|
|
local MAX_ACTIVE_TOASTS = 10
|
|
|
|
local DEFAULT_FADE_HOLD_TIME = 5
|
|
local DEFAULT_FADE_IN_TIME = 0.5
|
|
local DEFAULT_FADE_OUT_TIME = 1.2
|
|
local DEFAULT_TOAST_WIDTH = 250
|
|
local DEFAULT_TOAST_HEIGHT = 50
|
|
local DEFAULT_GLOW_WIDTH = 252
|
|
local DEFAULT_GLOW_HEIGHT = 56
|
|
local DEFAULT_ICON_SIZE = 30
|
|
local DEFAULT_OS_SPAWN_POINT = _G.IsMacClient() and "TOPRIGHT" or "BOTTOMRIGHT"
|
|
|
|
local DEFAULT_TOAST_BACKDROP = {
|
|
bgFile = [[Interface\FriendsFrame\UI-Toast-Background]],
|
|
edgeFile = [[Interface\FriendsFrame\UI-Toast-Border]],
|
|
tile = true,
|
|
tileSize = 12,
|
|
edgeSize = 12,
|
|
insets = {
|
|
left = 5,
|
|
right = 5,
|
|
top = 5,
|
|
bottom = 5,
|
|
},
|
|
}
|
|
|
|
local DEFAULT_BACKGROUND_COLORS = {
|
|
r = 0,
|
|
g = 0,
|
|
b = 0,
|
|
}
|
|
|
|
local DEFAULT_TITLE_COLORS = {
|
|
r = 0.510,
|
|
g = 0.773,
|
|
b = 1,
|
|
}
|
|
|
|
local DEFAULT_TEXT_COLORS = {
|
|
r = 0.486,
|
|
g = 0.518,
|
|
b = 0.541
|
|
}
|
|
|
|
local TOAST_BUTTONS = {
|
|
primary_button = true,
|
|
secondary_button = true,
|
|
tertiary_button = true,
|
|
}
|
|
local TOAST_BUTTON_HEIGHT = 18
|
|
|
|
local POINT_TRANSLATION = {
|
|
CENTER = DEFAULT_OS_SPAWN_POINT,
|
|
BOTTOM = "BOTTOMRIGHT",
|
|
BOTTOMLEFT = "BOTTOMLEFT",
|
|
BOTTOMRIGHT = "BOTTOMRIGHT",
|
|
LEFT = "TOPLEFT",
|
|
RIGHT = "TOPRIGHT",
|
|
TOP = "TOPRIGHT",
|
|
TOPLEFT = "TOPLEFT",
|
|
TOPRIGHT = "TOPRIGHT",
|
|
}
|
|
|
|
local SIBLING_ANCHORS = {
|
|
TOPRIGHT = "BOTTOMRIGHT",
|
|
TOPLEFT = "BOTTOMLEFT",
|
|
BOTTOMRIGHT = "TOPRIGHT",
|
|
BOTTOMLEFT = "TOPLEFT",
|
|
}
|
|
|
|
local OFFSET_X = {
|
|
TOPRIGHT = -20,
|
|
TOPLEFT = 20,
|
|
BOTTOMRIGHT = -20,
|
|
BOTTOMLEFT = 20,
|
|
}
|
|
|
|
local OFFSET_Y = {
|
|
TOPRIGHT = -30,
|
|
TOPLEFT = -30,
|
|
BOTTOMRIGHT = 30,
|
|
BOTTOMLEFT = 30,
|
|
}
|
|
|
|
local SIBLING_OFFSET_Y = {
|
|
TOPRIGHT = -10,
|
|
TOPLEFT = -10,
|
|
BOTTOMRIGHT = 10,
|
|
BOTTOMLEFT = 10,
|
|
}
|
|
|
|
local L_TOAST = "Toast"
|
|
local L_TOAST_DESC = "Shows messages in a toast window."
|
|
|
|
local LOCALE = _G.GetLocale()
|
|
|
|
if LOCALE == "esMX" or LOCALE == "esES" then
|
|
L_TOAST = "Información emergente"
|
|
L_TOAST_DESC = "Muestra mensajes de información en una ventana emergente"
|
|
elseif LOCALE == "frFR" then
|
|
L_TOAST_DESC = "Montrer les messages dans une fenêtre \"toast\"."
|
|
elseif LOCALE == "deDE" then
|
|
L_TOAST_DESC = "Zeigt Nachrichten in einem Toast-Fenster"
|
|
elseif LOCALE == "itIT" then
|
|
-- Nothing yet.
|
|
elseif LOCALE == "koKR" then
|
|
-- Nothing yet.
|
|
elseif LOCALE == "ptBR" then
|
|
L_TOAST = "Brinde"
|
|
L_TOAST_DESC = "Mostrar mensagems em uma janela externa"
|
|
elseif LOCALE == "ruRU" then
|
|
L_TOAST = "Всплывающее"
|
|
L_TOAST_DESC = "Показывать сообщения во всплывающем окне"
|
|
elseif LOCALE == "zhCN" then
|
|
L_TOAST = "弹出窗口"
|
|
L_TOAST_DESC = "在弹出窗口显示信息。"
|
|
elseif LOCALE == "zhTW" then
|
|
L_TOAST = "彈出視窗"
|
|
L_TOAST_DESC = "在彈出視窗顯示訊息。"
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Variables.
|
|
-----------------------------------------------------------------------
|
|
local QueuedAddOnName
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Settings functions.
|
|
-----------------------------------------------------------------------
|
|
local function ToastSpawnPoint()
|
|
return _G.Toaster and _G.Toaster:SpawnPoint() or DEFAULT_OS_SPAWN_POINT
|
|
end
|
|
|
|
local function ToastOffsetX()
|
|
return (_G.Toaster and _G.Toaster.SpawnOffsetX) and _G.Toaster:SpawnOffsetX() or nil
|
|
end
|
|
|
|
local function ToastOffsetY()
|
|
return (_G.Toaster and _G.Toaster.SpawnOffsetY) and _G.Toaster:SpawnOffsetY() or nil
|
|
end
|
|
|
|
local function ToastTitleColors(urgencyLevel)
|
|
if _G.Toaster then
|
|
return _G.Toaster:TitleColors(urgencyLevel)
|
|
else
|
|
return DEFAULT_TITLE_COLORS.r, DEFAULT_TITLE_COLORS.g, DEFAULT_TITLE_COLORS.b
|
|
end
|
|
end
|
|
|
|
local function ToastTextColors(urgencyLevel)
|
|
if _G.Toaster then
|
|
return _G.Toaster:TextColors(urgencyLevel)
|
|
else
|
|
return DEFAULT_TEXT_COLORS.r, DEFAULT_TEXT_COLORS.g, DEFAULT_TEXT_COLORS.b
|
|
end
|
|
end
|
|
|
|
local function ToastBackgroundColors(urgencyLevel)
|
|
if _G.Toaster then
|
|
return _G.Toaster:BackgroundColors(urgencyLevel)
|
|
else
|
|
return DEFAULT_BACKGROUND_COLORS.r, DEFAULT_BACKGROUND_COLORS.g, DEFAULT_BACKGROUND_COLORS.b
|
|
end
|
|
end
|
|
|
|
local function ToastDuration(addonName)
|
|
return _G.Toaster and _G.Toaster:Duration(addonName) or DEFAULT_FADE_HOLD_TIME
|
|
end
|
|
|
|
local function ToastOpacity(addonName)
|
|
return _G.Toaster and _G.Toaster:Opacity(addonName) or 0.75
|
|
end
|
|
|
|
local function ToastHasFloatingIcon(addonName)
|
|
return _G.Toaster and _G.Toaster:FloatingIcon(addonName)
|
|
end
|
|
|
|
local function ToastsAreSuppressed(addonName)
|
|
return _G.Toaster and (_G.Toaster:HideToasts() or _G.Toaster:HideToastsFromSource(addonName))
|
|
end
|
|
|
|
local function ToastsAreMuted(addonName)
|
|
return _G.Toaster and (_G.Toaster:MuteToasts() or _G.Toaster:MuteToastsFromSource(addonName))
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Helper functions.
|
|
-----------------------------------------------------------------------
|
|
local function AnimationHideParent(animation)
|
|
animation:GetParent():Hide()
|
|
end
|
|
|
|
local function GetEffectiveSpawnPoint(frame)
|
|
local x, y = frame:GetCenter()
|
|
if not x or not y then
|
|
return DEFAULT_OS_SPAWN_POINT
|
|
end
|
|
|
|
local horizontalName = (x > _G.UIParent:GetWidth() * 2 / 3) and "RIGHT" or (x < _G.UIParent:GetWidth() / 3) and "LEFT" or ""
|
|
local verticalName = (y > _G.UIParent:GetHeight() / 2) and "TOP" or "BOTTOM"
|
|
return verticalName .. horizontalName
|
|
end
|
|
|
|
local function GetCallingObject()
|
|
return CallingObject
|
|
end
|
|
|
|
local function StringValue(input)
|
|
local inputType = _G.type(input)
|
|
|
|
if inputType == "function" then
|
|
local output = input()
|
|
if _G.type(output) ~= "string" or output == "" then
|
|
return
|
|
end
|
|
|
|
return output
|
|
elseif inputType == "string" then
|
|
return input
|
|
end
|
|
end
|
|
|
|
if not LibToast.templates[LibToast.sink_template] then
|
|
LibToast.templates[LibToast.sink_template] = function(toast, text, _, _, _, _, _, _, _, _, iconTexture)
|
|
local callingObject = GetCallingObject()
|
|
toast:SetTitle(StringValue(LibToast.sink_titles[callingObject]))
|
|
toast:SetText(text)
|
|
toast:SetIconTexture(iconTexture or StringValue(LibToast.sink_icons[callingObject]))
|
|
end
|
|
end
|
|
|
|
local function _positionToastIcon(toast)
|
|
toast.icon:ClearAllPoints()
|
|
|
|
if ToastHasFloatingIcon(toast.addonName) then
|
|
local lowercasedPointName = POINT_TRANSLATION[GetEffectiveSpawnPoint(toast)]:lower()
|
|
|
|
if lowercasedPointName:find("right") then
|
|
toast.icon:SetPoint("TOPRIGHT", toast, "TOPLEFT", -5, -10)
|
|
elseif lowercasedPointName:find("left") then
|
|
toast.icon:SetPoint("TOPLEFT", toast, "TOPRIGHT", 5, -10)
|
|
end
|
|
else
|
|
toast.icon:SetPoint("TOPLEFT", toast, "TOPLEFT", 10, -10)
|
|
end
|
|
end
|
|
|
|
local function _reclaimButton(button)
|
|
button:Hide()
|
|
button:ClearAllPoints()
|
|
button:SetParent(nil)
|
|
button:SetText(nil)
|
|
table.insert(ButtonHeap, button)
|
|
end
|
|
|
|
local function _reclaimToast(toast)
|
|
for buttonName in pairs(TOAST_BUTTONS) do
|
|
local button = toast[buttonName]
|
|
|
|
if button then
|
|
toast[buttonName] = nil
|
|
_reclaimButton(button)
|
|
end
|
|
end
|
|
toast.is_persistent = nil
|
|
toast.templateName = nil
|
|
toast.payload = nil
|
|
toast.sound_file = nil
|
|
toast:Hide()
|
|
|
|
table.insert(ToastHeap, toast)
|
|
|
|
local removalIndex
|
|
for index = 1, #ActiveToasts do
|
|
if ActiveToasts[index] == toast then
|
|
removalIndex = index
|
|
break
|
|
end
|
|
end
|
|
|
|
if removalIndex then
|
|
table.remove(ActiveToasts, removalIndex):ClearAllPoints()
|
|
end
|
|
local spawnPoint = ToastSpawnPoint()
|
|
local offsetX = ToastOffsetX() or OFFSET_X[spawnPoint]
|
|
local offsetY = ToastOffsetY() or OFFSET_Y[spawnPoint]
|
|
|
|
for index = 1, #ActiveToasts do
|
|
local indexedToast = ActiveToasts[index]
|
|
indexedToast:ClearAllPoints()
|
|
|
|
_positionToastIcon(indexedToast)
|
|
|
|
if index == 1 then
|
|
indexedToast:SetPoint(spawnPoint, _G.UIParent, spawnPoint, offsetX, offsetY)
|
|
else
|
|
spawnPoint = POINT_TRANSLATION[GetEffectiveSpawnPoint(ActiveToasts[1])]
|
|
indexedToast:SetPoint(spawnPoint, ActiveToasts[index - 1], SIBLING_ANCHORS[spawnPoint], 0, SIBLING_OFFSET_Y[spawnPoint])
|
|
end
|
|
end
|
|
|
|
local toastData = table.remove(QueuedToasts, 1)
|
|
if toastData and toastData.addonName and _G.type(toastData.template) == "string" and toastData.template ~= "" then
|
|
QueuedAddOnName = toastData.addonName
|
|
LibToast:Spawn(toastData.template, _G.unpack(toastData.payload))
|
|
end
|
|
end
|
|
|
|
local function AnimationDismissToast(animation)
|
|
_reclaimToast(animation.toast)
|
|
end
|
|
|
|
local function Focus_OnEnter(frame, motion)
|
|
local toast = frame.toast
|
|
toast.dismiss_button:Show()
|
|
|
|
if not toast.is_persistent then
|
|
toast.waitAndAnimateOut:Stop()
|
|
toast.waitAndAnimateOut.animateOut:SetStartDelay(1)
|
|
end
|
|
end
|
|
|
|
local function Focus_OnLeave(frame, motion)
|
|
local toast = frame.toast
|
|
|
|
if not toast.dismiss_button:IsMouseOver() then
|
|
toast.dismiss_button:Hide()
|
|
end
|
|
|
|
if not toast.is_persistent then
|
|
toast.waitAndAnimateOut:Play()
|
|
end
|
|
end
|
|
|
|
local function _dismissToast(frame, button, down)
|
|
_reclaimToast(frame:GetParent())
|
|
end
|
|
|
|
local function _acquireToast(addonName)
|
|
local toast = table.remove(ToastHeap)
|
|
|
|
if not toast then
|
|
toast = _G.CreateFrame("Button", nil, _G.UIParent)
|
|
toast:SetFrameStrata("DIALOG")
|
|
toast:Hide()
|
|
|
|
local icon = toast:CreateTexture(nil, "BORDER")
|
|
icon:SetSize(DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE)
|
|
toast.icon = icon
|
|
|
|
local title = toast:CreateFontString(nil, "BORDER", "FriendsFont_Normal")
|
|
title:SetJustifyH("LEFT")
|
|
title:SetJustifyV("MIDDLE")
|
|
title:SetWordWrap(true)
|
|
title:SetPoint("TOPLEFT", toast, "TOPLEFT", 44, -10)
|
|
title:SetPoint("RIGHT", toast, "RIGHT", -20, 10)
|
|
toast.title = title
|
|
|
|
local focus = _G.CreateFrame("Frame", nil, toast)
|
|
focus:SetAllPoints(toast)
|
|
focus:SetScript("OnEnter", Focus_OnEnter)
|
|
focus:SetScript("OnLeave", Focus_OnLeave)
|
|
focus:SetScript("OnShow", Focus_OnLeave)
|
|
focus.toast = toast
|
|
|
|
local dismissButton = _G.CreateFrame("Button", nil, toast)
|
|
dismissButton:SetSize(18, 18)
|
|
dismissButton:SetPoint("TOPRIGHT", toast, "TOPRIGHT", -4, -4)
|
|
dismissButton:SetFrameStrata("DIALOG")
|
|
dismissButton:SetFrameLevel(toast:GetFrameLevel() + 2)
|
|
dismissButton:SetNormalTexture([[Interface\FriendsFrame\UI-Toast-CloseButton-Up]])
|
|
dismissButton:SetPushedTexture([[Interface\FriendsFrame\UI-Toast-CloseButton-Down]])
|
|
dismissButton:SetHighlightTexture([[Interface\FriendsFrame\UI-Toast-CloseButton-Highlight]])
|
|
dismissButton:Hide()
|
|
dismissButton:SetScript("OnClick", _dismissToast)
|
|
|
|
toast.dismiss_button = dismissButton
|
|
|
|
local text = toast:CreateFontString(nil, "BORDER", "FriendsFont_Normal")
|
|
text:SetJustifyH("LEFT")
|
|
text:SetJustifyV("MIDDLE")
|
|
text:SetWordWrap(true)
|
|
text:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
|
|
toast.text = text
|
|
|
|
local toastAnimateIn = toast:CreateAnimationGroup()
|
|
toast.animateIn = toastAnimateIn
|
|
|
|
local toastAnimateInFirst = toastAnimateIn:CreateAnimation("Alpha")
|
|
toastAnimateInFirst:SetOrder(1)
|
|
toastAnimateInFirst:SetFromAlpha(1)
|
|
toastAnimateInFirst:SetToAlpha(0)
|
|
toastAnimateInFirst:SetDuration(0)
|
|
|
|
local toastAnimateInSecond = toastAnimateIn:CreateAnimation("Alpha")
|
|
toastAnimateInSecond:SetOrder(2)
|
|
toastAnimateInSecond:SetFromAlpha(0)
|
|
toastAnimateInSecond:SetToAlpha(1)
|
|
toastAnimateInSecond:SetDuration(0.2)
|
|
|
|
local toastWaitAndAnimateOut = toast:CreateAnimationGroup()
|
|
toast.waitAndAnimateOut = toastWaitAndAnimateOut
|
|
|
|
local toastAnimateOut = toastWaitAndAnimateOut:CreateAnimation("Alpha")
|
|
toastAnimateOut:SetStartDelay(DEFAULT_FADE_HOLD_TIME)
|
|
toastAnimateOut:SetFromAlpha(1)
|
|
toastAnimateOut:SetToAlpha(0)
|
|
toastAnimateOut:SetDuration(DEFAULT_FADE_OUT_TIME)
|
|
toastAnimateOut:SetScript("OnFinished", AnimationDismissToast)
|
|
|
|
toastAnimateOut.toast = toast
|
|
toastWaitAndAnimateOut.animateOut = toastAnimateOut
|
|
|
|
local glowFrame = _G.CreateFrame("Frame", nil, toast)
|
|
glowFrame:SetAllPoints(toast)
|
|
toast.glowFrame = glowFrame
|
|
|
|
local glowTexture = glowFrame:CreateTexture(nil, "OVERLAY")
|
|
glowTexture:SetSize(DEFAULT_GLOW_WIDTH, DEFAULT_GLOW_HEIGHT)
|
|
glowTexture:SetPoint("TOPLEFT", -1, 3)
|
|
glowTexture:SetPoint("BOTTOMRIGHT", 1, -3)
|
|
glowTexture:SetTexture([[Interface\FriendsFrame\UI-Toast-Flair]])
|
|
glowTexture:SetBlendMode("ADD")
|
|
glowTexture:Hide()
|
|
|
|
glowFrame.glow = glowTexture
|
|
|
|
local glowAnimateIn = glowTexture:CreateAnimationGroup()
|
|
glowAnimateIn:SetScript("OnFinished", AnimationHideParent)
|
|
glowTexture.animateIn = glowAnimateIn
|
|
|
|
local glowAnimateInFirst = glowAnimateIn:CreateAnimation("Alpha")
|
|
glowAnimateInFirst:SetOrder(1)
|
|
glowAnimateInFirst:SetFromAlpha(0)
|
|
glowAnimateInFirst:SetToAlpha(1)
|
|
glowAnimateInFirst:SetDuration(0.2)
|
|
|
|
local glowAnimateInSecond = glowAnimateIn:CreateAnimation("Alpha")
|
|
glowAnimateInSecond:SetOrder(2)
|
|
glowAnimateInSecond:SetFromAlpha(1)
|
|
glowAnimateInSecond:SetToAlpha(0)
|
|
glowAnimateInSecond:SetDuration(0.5)
|
|
end
|
|
|
|
toast:SetSize(DEFAULT_TOAST_WIDTH, DEFAULT_TOAST_HEIGHT)
|
|
toast:SetBackdrop(DEFAULT_TOAST_BACKDROP)
|
|
toast:SetBackdropBorderColor(1, 1, 1)
|
|
|
|
if _G.Toaster then
|
|
local iconSize = _G.Toaster:IconSize(addonName)
|
|
toast.icon:SetSize(iconSize, iconSize)
|
|
end
|
|
|
|
return toast
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Library methods.
|
|
-----------------------------------------------------------------------
|
|
function LibToast:Register(templateName, constructor, isUnique)
|
|
local isLib = (self == LibToast)
|
|
|
|
if _G.type(templateName) ~= "string" or templateName == "" then
|
|
error(METHOD_USAGE_FORMAT:format(isLib and "Register" or "RegisterToast", "templateName must be a non-empty string"), 2)
|
|
end
|
|
|
|
if _G.type(constructor) ~= "function" then
|
|
error(METHOD_USAGE_FORMAT:format(isLib and "Register" or "RegisterToast", "constructor must be a function"), 2)
|
|
end
|
|
|
|
LibToast.templates[templateName] = constructor
|
|
LibToast.unique_templates[templateName] = isUnique or nil
|
|
end
|
|
|
|
function LibToast:Spawn(templateName, ...)
|
|
local isLib = (self == LibToast)
|
|
|
|
if not templateName or (not IsInternalCall and (_G.type(templateName) ~= "string" or templateName == "")) then
|
|
error(METHOD_USAGE_FORMAT:format(isLib and "Spawn" or "SpawnToast", "templateName must be a non-empty string"), 2)
|
|
end
|
|
|
|
if not LibToast.templates[templateName] then
|
|
error(METHOD_USAGE_FORMAT:format(isLib and "Spawn" or "SpawnToast", ("\"%s\" does not match a registered template"):format(templateName)), 2)
|
|
end
|
|
|
|
local addonName
|
|
if QueuedAddOnName then
|
|
addonName = QueuedAddOnName
|
|
QueuedAddOnName = nil
|
|
elseif isLib then
|
|
addonName = _G.select(3, ([[\]]):split(_G.debugstack(2)))
|
|
else
|
|
addonName = LibToast.addon_names[self] or _G.UNKNOWN
|
|
end
|
|
|
|
if ToastsAreSuppressed(addonName) then
|
|
return
|
|
end
|
|
|
|
if LibToast.unique_templates[templateName] then
|
|
for index = 1, #ActiveToasts do
|
|
if ActiveToasts[index].templateName == templateName then
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
if #ActiveToasts >= MAX_ACTIVE_TOASTS then
|
|
table.insert(QueuedToasts, {
|
|
addonName = addonName,
|
|
template = templateName,
|
|
payload = { ... }
|
|
})
|
|
return
|
|
end
|
|
|
|
CurrentToast = _acquireToast(addonName)
|
|
CurrentToast.templateName = templateName
|
|
CurrentToast.addonName = addonName
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Reset defaults.
|
|
-----------------------------------------------------------------------
|
|
CurrentToast.title:SetText(nil)
|
|
CurrentToast.text:SetText(nil)
|
|
CurrentToast.icon:SetTexture(nil)
|
|
CurrentToast.icon:SetTexCoord(0, 1, 0, 1)
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Run constructor.
|
|
-----------------------------------------------------------------------
|
|
CallingObject = self
|
|
LibToast.templates[templateName](ToastProxy, ...)
|
|
|
|
if not CurrentToast.title:GetText() and not CurrentToast.text:GetText() and not CurrentToast.icon:GetTexture() then
|
|
_reclaimToast(CurrentToast)
|
|
return
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Finalize layout.
|
|
-----------------------------------------------------------------------
|
|
local urgency = CurrentToast.urgency_level
|
|
CurrentToast.title:SetTextColor(ToastTitleColors(urgency))
|
|
CurrentToast.text:SetTextColor(ToastTextColors(urgency))
|
|
|
|
local opacity = ToastOpacity(addonName)
|
|
local r, g, b = ToastBackgroundColors(urgency)
|
|
CurrentToast:SetBackdropColor(r, g, b, opacity)
|
|
|
|
r, g, b = CurrentToast:GetBackdropBorderColor()
|
|
CurrentToast:SetBackdropBorderColor(r, g, b, opacity)
|
|
|
|
if ToastHasFloatingIcon(addonName) or not CurrentToast.icon:GetTexture() then
|
|
CurrentToast.title:SetPoint("TOPLEFT", CurrentToast, "TOPLEFT", 10, -10)
|
|
else
|
|
CurrentToast.title:SetPoint("TOPLEFT", CurrentToast, "TOPLEFT", CurrentToast.icon:GetWidth() + 15, -10)
|
|
end
|
|
|
|
if CurrentToast.title:GetText() then
|
|
CurrentToast.title:SetWidth(CurrentToast:GetWidth() - CurrentToast.icon:GetWidth() - 20)
|
|
CurrentToast.title:Show()
|
|
else
|
|
CurrentToast.title:Hide()
|
|
end
|
|
|
|
if CurrentToast.text:GetText() then
|
|
CurrentToast.text:SetWidth(CurrentToast:GetWidth() - CurrentToast.icon:GetWidth() - 20)
|
|
CurrentToast.text:Show()
|
|
else
|
|
CurrentToast.text:Hide()
|
|
end
|
|
local buttonHeight = (CurrentToast.primary_button or CurrentToast.secondary_button or CurrentToast.tertiary_button) and TOAST_BUTTON_HEIGHT or 0
|
|
CurrentToast:SetHeight(CurrentToast.text:GetStringHeight() + CurrentToast.title:GetStringHeight() + buttonHeight + 25)
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Anchor and spawn.
|
|
-----------------------------------------------------------------------
|
|
local spawnPoint = ToastSpawnPoint()
|
|
local offsetX = ToastOffsetX() or OFFSET_X[spawnPoint]
|
|
local offsetY = ToastOffsetY() or OFFSET_Y[spawnPoint]
|
|
|
|
if #ActiveToasts > 0 then
|
|
spawnPoint = POINT_TRANSLATION[GetEffectiveSpawnPoint(ActiveToasts[1])]
|
|
CurrentToast:SetPoint(spawnPoint, ActiveToasts[#ActiveToasts], SIBLING_ANCHORS[spawnPoint], 0, SIBLING_OFFSET_Y[spawnPoint])
|
|
else
|
|
CurrentToast:SetPoint(spawnPoint, _G.UIParent, spawnPoint, offsetX, offsetY)
|
|
end
|
|
|
|
ActiveToasts[#ActiveToasts + 1] = CurrentToast
|
|
|
|
_positionToastIcon(CurrentToast)
|
|
|
|
if CurrentToast.sound_file and not ToastsAreMuted(addonName) then
|
|
_G.PlaySoundFile(CurrentToast.sound_file)
|
|
end
|
|
|
|
CurrentToast:Show()
|
|
CurrentToast.animateIn:Play()
|
|
CurrentToast.glowFrame.glow:Show()
|
|
CurrentToast.glowFrame.glow.animateIn:Play()
|
|
CurrentToast.waitAndAnimateOut:Stop() -- Stop prior fade attempt.
|
|
|
|
if not CurrentToast.is_persistent then
|
|
if CurrentToast:IsMouseOver() then
|
|
CurrentToast.waitAndAnimateOut.animateOut:SetStartDelay(1)
|
|
else
|
|
CurrentToast.waitAndAnimateOut.animateOut:SetStartDelay(ToastDuration(addonName))
|
|
CurrentToast.waitAndAnimateOut:Play()
|
|
end
|
|
end
|
|
end
|
|
|
|
function LibToast:DefineSink(displayName, texturePath)
|
|
local isLib = (self == LibToast)
|
|
local texturePathType = _G.type(texturePath)
|
|
local displayNameType = _G.type(displayName)
|
|
|
|
if texturePath and (texturePathType ~= "function" and (texturePathType ~= "string" or texturePath == "")) then
|
|
error(METHOD_USAGE_FORMAT:format(isLib and "DefineSink" or "DefineSinkToast", "texturePath must be a non-empty string, a function that returns one, or nil"), 2)
|
|
end
|
|
if displayName and (displayNameType ~= "function" and (displayNameType ~= "string" or displayName == "")) then
|
|
error(METHOD_USAGE_FORMAT:format(isLib and "DefineSink" or "DefineSinkToast", "displayName must be a non-empty string, a function that returns one, or nil"), 2)
|
|
end
|
|
|
|
local addonName = _G.select(3, ([[\]]):split(_G.debugstack(2)))
|
|
LibToast.addon_names[self] = addonName or _G.UNKNOWN
|
|
LibToast.sink_icons[self] = texturePath
|
|
LibToast.sink_titles[self] = displayName
|
|
|
|
if not LibToast.registered_sink then
|
|
local LibSink = LibStub("LibSink-2.0")
|
|
if not LibSink then
|
|
return
|
|
end
|
|
|
|
LibSink:RegisterSink("LibToast-1.0", L_TOAST, L_TOAST_DESC, function(caller, text, ...)
|
|
IsInternalCall = true
|
|
|
|
local func
|
|
if caller.SpawnToast then
|
|
func = caller.SpawnToast
|
|
else
|
|
caller = LibToast
|
|
func = LibToast.Spawn
|
|
end
|
|
|
|
func(caller, LibToast.sink_template, text, ...)
|
|
IsInternalCall = nil
|
|
end)
|
|
|
|
LibToast.registered_sink = true
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Proxy methods.
|
|
-----------------------------------------------------------------------
|
|
local TOAST_URGENCIES = {
|
|
very_low = true,
|
|
moderate = true,
|
|
normal = true,
|
|
high = true,
|
|
emergency = true,
|
|
}
|
|
|
|
function ToastProxy:SetUrgencyLevel(urgencyLevel)
|
|
urgencyLevel = urgencyLevel:gsub(" ", "_"):lower()
|
|
|
|
if not TOAST_URGENCIES[urgencyLevel] then
|
|
error(("\"%s\" is not a valid toast urgency level"):format(urgencyLevel), 2)
|
|
end
|
|
CurrentToast.urgency_level = urgencyLevel
|
|
end
|
|
|
|
function ToastProxy:UrgencyLevel()
|
|
return CurrentToast.urgency_level
|
|
end
|
|
|
|
function ToastProxy:SetTitle(title)
|
|
CurrentToast.title:SetText(title)
|
|
end
|
|
|
|
function ToastProxy:SetFormattedTitle(title, ...)
|
|
CurrentToast.title:SetFormattedText(title, ...)
|
|
end
|
|
|
|
function ToastProxy:SetText(text)
|
|
CurrentToast.text:SetText(text)
|
|
end
|
|
|
|
function ToastProxy:SetFormattedText(text, ...)
|
|
CurrentToast.text:SetFormattedText(text, ...)
|
|
end
|
|
|
|
function ToastProxy:SetIconAtlas(...)
|
|
CurrentToast.icon:SetAtlas(...)
|
|
end
|
|
|
|
function ToastProxy:SetIconTexture(texture)
|
|
CurrentToast.icon:SetTexture(texture)
|
|
end
|
|
|
|
function ToastProxy:SetIconTexCoord(...)
|
|
CurrentToast.icon:SetTexCoord(...)
|
|
end
|
|
|
|
local _initializedToastButton
|
|
do
|
|
local BUTTON_NAME_FORMAT = "LibToast_Button%d"
|
|
local button_count = 0
|
|
|
|
local function _buttonCallbackHandler(button, mouseButtonName, isPress)
|
|
button.handler(button.id, mouseButtonName, isPress, button.toast.payload)
|
|
_reclaimToast(button.toast)
|
|
end
|
|
|
|
local function _acquireToastButton(toast)
|
|
local button = table.remove(ButtonHeap)
|
|
|
|
if not button then
|
|
button_count = button_count + 1
|
|
|
|
button = _G.CreateFrame("Button", BUTTON_NAME_FORMAT:format(button_count), toast, "UIMenuButtonStretchTemplate")
|
|
button:SetHeight(TOAST_BUTTON_HEIGHT)
|
|
button:SetFrameStrata("DIALOG")
|
|
button:SetScript("OnClick", _buttonCallbackHandler)
|
|
|
|
local fontString = button:GetFontString()
|
|
fontString:SetJustifyH("CENTER")
|
|
fontString:SetJustifyV("CENTER")
|
|
end
|
|
|
|
button:SetParent(toast)
|
|
button:SetFrameLevel(toast:GetFrameLevel() + 2)
|
|
return button
|
|
end
|
|
|
|
function _initializedToastButton(buttonID, label, handler)
|
|
if not label or not handler then
|
|
error("label and handler are required", 3)
|
|
return
|
|
end
|
|
|
|
local button = CurrentToast[buttonID]
|
|
if not button then
|
|
button = _acquireToastButton(CurrentToast)
|
|
CurrentToast[buttonID] = button
|
|
end
|
|
|
|
button.id = buttonID:gsub("_button", "")
|
|
button.handler = handler
|
|
button.toast = CurrentToast
|
|
|
|
button:Show()
|
|
button:SetText(label)
|
|
button:SetWidth(button:GetFontString():GetStringWidth() + 15)
|
|
|
|
return button
|
|
end
|
|
end -- do-block
|
|
|
|
function ToastProxy:SetPrimaryCallback(label, handler)
|
|
local button = _initializedToastButton("primary_button", label, handler)
|
|
button:SetPoint("BOTTOMLEFT", CurrentToast, "BOTTOMLEFT", 3, 4)
|
|
button:SetPoint("BOTTOMRIGHT", CurrentToast, "BOTTOMRIGHT", -3, 4)
|
|
|
|
CurrentToast:SetHeight(CurrentToast:GetHeight() + button:GetHeight() + 5)
|
|
|
|
if button:GetWidth() > CurrentToast:GetWidth() then
|
|
CurrentToast:SetWidth(button:GetWidth() + 5)
|
|
end
|
|
end
|
|
|
|
function ToastProxy:SetSecondaryCallback(label, handler)
|
|
if not CurrentToast.primary_button then
|
|
error("primary button must be defined first", 2)
|
|
end
|
|
CurrentToast.primary_button:ClearAllPoints()
|
|
CurrentToast.primary_button:SetPoint("BOTTOMLEFT", CurrentToast, "BOTTOMLEFT", 3, 4)
|
|
|
|
local button = _initializedToastButton("secondary_button", label, handler)
|
|
button:SetPoint("BOTTOMRIGHT", CurrentToast, "BOTTOMRIGHT", -3, 4)
|
|
|
|
if button:GetWidth() + CurrentToast.primary_button:GetWidth() > CurrentToast:GetWidth() then
|
|
CurrentToast:SetWidth(button:GetWidth() + CurrentToast.primary_button:GetWidth() + 5)
|
|
end
|
|
end
|
|
|
|
function ToastProxy:SetTertiaryCallback(label, handler)
|
|
if not CurrentToast.primary_button or not CurrentToast.secondary_button then
|
|
error("primary and secondary buttons must be defined first", 2)
|
|
end
|
|
CurrentToast.secondary_button:ClearAllPoints()
|
|
CurrentToast.secondary_button:SetPoint("LEFT", CurrentToast.primary_button, "RIGHT", 0, 0)
|
|
|
|
local button = _initializedToastButton("tertiary_button", label, handler)
|
|
button:SetPoint("LEFT", CurrentToast.secondary_button, "RIGHT", 0, 0)
|
|
|
|
if button:GetWidth() + CurrentToast.primary_button:GetWidth() + CurrentToast.secondary_button:GetWidth() > CurrentToast:GetWidth() then
|
|
CurrentToast:SetWidth(button:GetWidth() + CurrentToast.primary_button:GetWidth() + CurrentToast.secondary_button:GetWidth() + 5)
|
|
end
|
|
end
|
|
|
|
function ToastProxy:SetPayload(...)
|
|
CurrentToast.payload = { ... }
|
|
end
|
|
|
|
function ToastProxy:Payload()
|
|
return _G.unpack(CurrentToast.payload)
|
|
end
|
|
|
|
function ToastProxy:MakePersistent()
|
|
CurrentToast.is_persistent = true
|
|
end
|
|
|
|
function ToastProxy:SetSoundFile(filePath)
|
|
CurrentToast.sound_file = filePath
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Embed handling.
|
|
-----------------------------------------------------------------------
|
|
LibToast.embeds = LibToast.embeds or {}
|
|
|
|
local mixins = {
|
|
"DefineSink",
|
|
"Register",
|
|
"Spawn",
|
|
}
|
|
|
|
function LibToast:Embed(target)
|
|
LibToast.embeds[target] = true
|
|
|
|
for index = 1, #mixins do
|
|
local method = mixins[index]
|
|
target[method .. "Toast"] = LibToast[method]
|
|
end
|
|
return target
|
|
end
|
|
|
|
for addon in pairs(LibToast.embeds) do
|
|
LibToast:Embed(addon)
|
|
end
|
|
|