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

-----------------------------------------------------------------------
-- 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