In Civilization, the notifications don’t go away until the user interacts with them, you can often have more of them than even fit on the screen, and the notification list is part of a specific screen, rather than being an “overlay”–other things can appear on top of notifications, and the notifications persist if you leave the screen and then come back. I’d describe that more as a “log” than as “toasts”, though I suppose a lot of the same implementation guidelines apply.
(In fact, I think Civ would benefit from having some sort of “notification history” screen, where you can see notifications that you’ve already dismissed or from previous turns. But that’s probably beyond scope for this thread.)
You can modify a Button at runtime to call any function you want, using Button.onClick.AddListener. This will only directly allow you to add a function with no arguments, but you can easily get around this by wrapping your function call in a lambda expression. For instance, if you want the button to call MyFunction(7, 42), you could write
button.onClick.AddListener( () => MyFunction(7, 42) );
Warning: If your lambda expression contains a variable, it saves a reference to that variable, not its current value! This often trips people up when they want to create a bunch of lambda in a loop; e.g.
for (int i = 0; i < 10; ++i)
{
myButtons[i].onClick.AddListener(()=>MyFunction(i));
}
// COMMON MISTAKE -
// the above code will cause every button to call MyFunction(10),
// because by the time they get CLICKED, i == 10
// instead, do this:
for (int i = 0; i < 10; ++i)
{
int temp = i;
myButtons[i].onClick.AddListener(()=>MyFunction(temp));
}
// This uses a separate variable (scoped to just that loop iteration)
// for each button
So, one approach is to have the code that creates the notification also figure out exactly what function should be called (with what parameters) when it gets clicked, and then add that as a listener to the button.
But you also might want to think about putting some of the intelligence “after” the click, rather than before. For instance, if you have a notification saying that your production queue is empty in the city of Rome, then instead of making the button call OpenProductionQueue(Rome), you might want to have it call ClickedOnNotification(EmptyQueue, Rome). This way, you can decide to make clicking on the notification do different things based on the situation at the time it’s clicked, instead of only at the time it’s created.
(Also, you might want the notification object itself to be one of the parameters you pass to the function–then the function can decide to do things like delete the notification, if you want.)
EDIT: Oh! You also might want to take a few minutes before you start to think about how you are going to serialize your notifications–if your game is similar to Civilization, then you probably want them to persist if the user saves the game and then loads it tomorrow! (Endless Legend/Endless Space fail to save their notifications, and it drives me crazy.)
This will push you more towards having all notifications call something like ClickedOnNotifcation(someNotificationVariable) and then read all necessary data out of the notification object, because serializing anonymous functions is somewhat problematic.