1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: ui.hpp
5 	Desc: contains interface related declarations
6 
7 	Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved.
8 	See LICENSE for details.
9 
10 -------------------------------------------------------------------------------*/
11 
12 #pragma once
13 
14 #include "../main.hpp"
15 #include "../game.hpp"
16 #include "../files.hpp"
17 #include "../draw.hpp"
18 #include "interface.hpp"
19 #include "../colors.hpp"
20 
21 class UIToastNotification
22 {
23 	double scalex = 0.25;
24 	double scaley = 0.25;
25 	int posx = 260;
26 	int posy = 110;
27 	int showHeight = 0;
28 	int dockHeight = 32;
29 	SDL_Surface* notificationImage = nullptr;
30 	bool isInit = false;
31 	int textx = 8;
32 	int texty = 0;
33 	int bodyx = 12;
34 	int bodyy = 16;
35 
36 	SDL_Rect padding;
37 	int actionButtonOffsetY = 0;
38 	int actionButtonOffsetW = 0;
39 
40 	int kImageBorderHeight = 256;
41 	int kImageBorderWidth = 256;
42 
43 	bool mainCardHide = false;
44 	bool mainCardIsHidden = false;
45 	int cardWidth = 332;
46 	int animx = cardWidth;
47 	int anim_ticks = 0;
48 	int anim_duration = 50;
49 
50 	bool dockedCardHide = true;
51 	bool dockedCardIsHidden = true;
52 	int dockedCardWidth = 48;
53 	int docked_animx = dockedCardWidth;
54 	int docked_anim_ticks = 0;
55 	int docked_anim_duration = 25;
56 
57 	Uint32 cardState = UI_CARD_STATE_SHOW;
58 	bool temporaryCardHide = false;
59 	bool idleDisappear = true;
60 	Uint32 lastInteractedTick = 0;
61 	bool cardUpdateDisplayMainText = false;
62 	bool cardUpdateDisplaySecondaryText = false;
63 	Uint32 idleTicksToHide = 10 * TICKS_PER_SECOND;
64 
65 	std::string displayedText;
66 	std::string mainCardText;
67 	std::string secondaryCardText;
68 	std::string headerCardText;
69 	std::string actionText;
70 
71 	int statisticUpdateCurrent = 0;
72 	int statisticUpdateMax = 0;
73 	int pendingStatisticUpdateCurrent = -1;
74 	std::string achievementID = "";
75 public:
UIToastNotification(SDL_Surface * image)76 	UIToastNotification(SDL_Surface* image) {
77 		notificationImage = image;
78 		showHeight = static_cast<int>(kImageBorderHeight * scaley + 16);
79 		padding.x = 0;
80 		padding.y = 0;
81 		padding.w = 0;
82 		padding.h = 0;
83 	};
~UIToastNotification()84 	~UIToastNotification(){};
85 
86 	Uint32 actionFlags = 0;
87 	enum ActionFlags : Uint32
88 	{
89 		UI_NOTIFICATION_CLOSE = 1,
90 		UI_NOTIFICATION_ACTION_BUTTON = 2,
91 		UI_NOTIFICATION_AUTO_HIDE = 4,
92 		UI_NOTIFICATION_RESET_TEXT_TO_MAIN_ON_HIDE = 8,
93 		UI_NOTIFICATION_REMOVABLE = 16,
94 		UI_NOTIFICATION_STATISTIC_UPDATE = 32
95 	};
96 	enum CardType : Uint32
97 	{
98 		UI_CARD_DEFAULT,
99 		UI_CARD_EOS_ACCOUNT,
100 		UI_CARD_CROSSPLAY_ACCOUNT,
101 		UI_CARD_COMMUNITY_LINK,
102 		UI_CARD_ACHIEVEMENT,
103 		UI_CARD_PROMO
104 	};
105 	CardType cardType = CardType::UI_CARD_DEFAULT;
106 	enum CardState : Uint32
107 	{
108 		UI_CARD_STATE_SHOW,
109 		UI_CARD_STATE_DOCKED,
110 		UI_CARD_STATE_UPDATE,
111 		UI_CARD_STATE_REMOVED
112 	};
113 	void (*buttonAction)() = nullptr;
114 	bool skipDrawingCardThisTick = false;
115 	bool bQueuedForUndock = false;
getDimensions(int & outPosX,int & outPosY,int & outPosW,int & outPosH)116 	void getDimensions(int& outPosX, int& outPosY, int& outPosW, int& outPosH)
117 	{
118 		outPosX = posx;
119 		outPosY = posy;
120 		outPosW = cardWidth;
121 		outPosH = showHeight;
122 	}
setDimensions(SDL_Rect & pos)123 	void setDimensions(SDL_Rect& pos)
124 	{
125 		posx = pos.x;
126 		posy = pos.y;
127 		cardWidth = pos.w;
128 		animx = cardWidth;
129 		showHeight = pos.h;
130 	}
setPadding(SDL_Rect & pos)131 	void setPadding(SDL_Rect& pos)
132 	{
133 		padding.x = pos.x;
134 		padding.y = pos.y;
135 		padding.w = pos.w;
136 		padding.h = pos.h;
137 	}
setImageDimensions(int width,int height)138 	void setImageDimensions(int width, int height)
139 	{
140 		kImageBorderWidth = width;
141 		kImageBorderHeight = height;
142 	}
setImageScale(double x,double y)143 	void setImageScale(double x, double y)
144 	{
145 		scalex = x;
146 		scaley = y;
147 	}
setPosY(int y)148 	void setPosY(int y)
149 	{
150 		posy = y;
151 	}
setActionButtonOffsetY(int y)152 	void setActionButtonOffsetY(int y)
153 	{
154 		actionButtonOffsetY = y;
155 	}
setActionButtonOffsetW(int w)156 	void setActionButtonOffsetW(int w)
157 	{
158 		actionButtonOffsetW = w;
159 	}
setMainText(const std::string & text)160 	void setMainText(const std::string& text)
161 	{
162 		mainCardText = text;
163 	}
setSecondaryText(const std::string & text)164 	void setSecondaryText(const std::string& text)
165 	{
166 		secondaryCardText = text;
167 	}
setHeaderText(const std::string & text)168 	void setHeaderText(const std::string& text)
169 	{
170 		headerCardText = text;
171 	}
setDisplayedText(const std::string & text)172 	void setDisplayedText(const std::string& text)
173 	{
174 		displayedText = text;
175 	}
setActionText(const std::string & text)176 	void setActionText(const std::string& text)
177 	{
178 		actionText = text;
179 	}
setIdleSeconds(Uint32 seconds)180 	void setIdleSeconds(Uint32 seconds)
181 	{
182 		idleTicksToHide = seconds * TICKS_PER_SECOND;
183 	}
setStatisticCurrentValue(int value)184 	void setStatisticCurrentValue(int value)
185 	{
186 		statisticUpdateCurrent = value;
187 	}
setStatisticMaxValue(int value)188 	void setStatisticMaxValue(int value)
189 	{
190 		statisticUpdateMax = value;
191 	}
setAchievementName(const char * achName)192 	void setAchievementName(const char* achName)
193 	{
194 		achievementID = achName;
195 	}
matchesAchievementName(const char * achName)196 	bool matchesAchievementName(const char* achName)
197 	{
198 		return (achievementID.compare(achName) == 0);
199 	}
getMainText()200 	std::string& getMainText() { return mainCardText; };
getCardState()201 	CardState getCardState() { return static_cast<CardState>(cardState); }
202 
draw()203 	void draw()
204 	{
205 		if ( !isInit )
206 		{
207 			return;
208 		}
209 
210 		if ( subwindow || fadeout )
211 		{
212 			if ( !fadeout && !(actionFlags & UI_NOTIFICATION_AUTO_HIDE) )
213 			{
214 				// don't hide or close
215 			}
216 			else if ( (fadeout && cardType == UI_CARD_ACHIEVEMENT) )
217 			{
218 				// don't hide or close
219 			}
220 			else
221 			{
222 				temporaryCardHide = (actionFlags & UI_NOTIFICATION_AUTO_HIDE);
223 				lastInteractedTick = ticks;
224 				if ( cardState == UI_CARD_STATE_SHOW )
225 				{
226 					mainCardHide = true;
227 					dockedCardHide = false;
228 				}
229 			}
230 		}
231 		else
232 		{
233 			temporaryCardHide = false;
234 		}
235 
236 		if ( cardState == UI_CARD_STATE_SHOW || cardState == UI_CARD_STATE_UPDATE )
237 		{
238 			drawMainCard();
239 		}
240 		else if ( cardState == UI_CARD_STATE_DOCKED )
241 		{
242 			drawDockedCard();
243 		}
244 	}
245 
undockCard()246 	void undockCard()
247 	{
248 		dockedCardHide = true;
249 		mainCardHide = false;
250 		lastInteractedTick = ticks;
251 	}
252 
drawDockedCard()253 	void drawDockedCard()
254 	{
255 		SDL_Rect r;
256 		r.w = 32;
257 		r.h = static_cast<int>(kImageBorderHeight * scaley) + padding.h;
258 		r.x = xres - r.w + docked_animx;
259 		r.y = yres - r.h - posy;
260 		drawWindowFancy(r.x, r.y - 8, xres + 16 + docked_animx, r.y + 24);
261 
262 		if ( !temporaryCardHide && mouseInBounds(r.x, xres + docked_animx, r.y - 8, r.y + 24) )
263 		{
264 			if ( mousestatus[SDL_BUTTON_LEFT] )
265 			{
266 				mousestatus[SDL_BUTTON_LEFT] = 0;
267 				undockCard();
268 			}
269 		}
270 		ttfPrintTextColor(ttf16, r.x + 8, r.y + texty,
271 			SDL_MapRGBA(mainsurface->format, 255, 255, 0, 255), true, "<");
272 
273 		if ( temporaryCardHide )
274 		{
275 			animate(docked_animx, docked_anim_ticks, docked_anim_duration, dockedCardWidth, false, dockedCardIsHidden);
276 		}
277 		else
278 		{
279 			animate(docked_animx, docked_anim_ticks, docked_anim_duration, dockedCardWidth, dockedCardHide, dockedCardIsHidden);
280 			if ( dockedCardHide && dockedCardIsHidden )
281 			{
282 				cardState = UI_CARD_STATE_SHOW;
283 			}
284 		}
285 	}
286 
hideMainCard()287 	void hideMainCard()
288 	{
289 		mainCardHide = true;
290 		dockedCardHide = false;
291 		lastInteractedTick = ticks;
292 	}
showMainCard()293 	void showMainCard()
294 	{
295 		mainCardHide = false;
296 		dockedCardHide = true;
297 		lastInteractedTick = ticks;
298 	}
cardForceTickUpdate()299 	void cardForceTickUpdate()
300 	{
301 		lastInteractedTick = ticks;
302 	}
updateCardStatisticEvent(int updatedValue)303 	void updateCardStatisticEvent(int updatedValue)
304 	{
305 		pendingStatisticUpdateCurrent = updatedValue;
306 		updateCardEvent(true, false);
307 	}
updateCardEvent(bool updateMainText,bool updateSecondaryText)308 	void updateCardEvent(bool updateMainText, bool updateSecondaryText)
309 	{
310 		lastInteractedTick = ticks;
311 		cardState = UI_CARD_STATE_UPDATE;
312 		if ( updateMainText )
313 		{
314 			cardUpdateDisplayMainText = true;
315 			cardUpdateDisplaySecondaryText = false;
316 		}
317 		if ( updateSecondaryText )
318 		{
319 			cardUpdateDisplayMainText = false;
320 			cardUpdateDisplaySecondaryText = true;
321 		}
322 	}
323 
drawProgressBar(SDL_Rect & imageDimensions)324 	void drawProgressBar(SDL_Rect& imageDimensions)
325 	{
326 		int percent = (int)floor(statisticUpdateCurrent * 100 / static_cast<double>(statisticUpdateMax));
327 
328 		SDL_Rect progressbar;
329 		progressbar.x = imageDimensions.x + imageDimensions.w + textx;
330 		progressbar.h = TTF12_HEIGHT + 2;
331 		progressbar.w = (xres - 8 + animx) - progressbar.x - 10;
332 		progressbar.y = imageDimensions.y + imageDimensions.h - progressbar.h;
333 		drawWindowFancy(progressbar.x - 2, progressbar.y - 2, progressbar.x + progressbar.w + 2, progressbar.y + progressbar.h + 2);
334 
335 		drawRect(&progressbar, SDL_MapRGB(mainsurface->format, 36, 36, 36), 255);
336 		progressbar.w = std::min((xres - 8 + animx) - progressbar.x - 4, static_cast<int>(progressbar.w * percent / 100.0));
337 		drawRect(&progressbar, uint32ColorBaronyBlue(*mainsurface), 92);
338 		progressbar.w = (xres - 8 + animx) - progressbar.x - TTF12_WIDTH;
339 
340 		char progress_str[32] = { 0 };
341 		snprintf(progress_str, sizeof(progress_str), "%d / %d", statisticUpdateCurrent, statisticUpdateMax);
342 		ttfPrintTextColor(ttf12, progressbar.x + progressbar.w / 2 - (strlen(progress_str) * TTF12_WIDTH) / 2,
343 			progressbar.y + 4, uint32ColorWhite(*mainsurface), true, progress_str);
344 	}
345 
drawMainCard()346 	void drawMainCard()
347 	{
348 		SDL_Rect r;
349 		r.w = static_cast<int>(kImageBorderWidth * scalex);
350 		r.h = static_cast<int>(kImageBorderHeight * scaley) + padding.h;
351 		r.x = xres - r.w - posx + animx + 12;
352 		r.y = yres - r.h - posy;
353 		drawWindowFancy(r.x - 8, r.y - 8, xres - 8 + animx, r.y + r.h + 8);
354 		drawCloseButton(&r);
355 
356 		if ( cardType == UI_CARD_PROMO )
357 		{
358 			// draw text centred
359 			Uint32 centrex = r.x + (r.w / 2);
360 			Uint32 textx = centrex - (headerCardText.length() * TTF12_WIDTH) / 2;
361 			ttfPrintTextColor(ttf12, textx, r.y + texty + padding.y, SDL_MapRGBA(mainsurface->format, 255, 255, 0, 255), true,
362 				headerCardText.c_str());
363 
364 			char c[256] = "";
365 			strcpy(c, displayedText.c_str());
366 			textx = centrex - (longestline(c) * TTF12_WIDTH) / 2;
367 			ttfPrintTextColor(ttf12, textx, r.y + bodyy + padding.y, SDL_MapRGBA(mainsurface->format, 255, 255, 255, 255), true,
368 				displayedText.c_str());
369 		}
370 		else
371 		{
372 			ttfPrintTextColor(ttf12, r.x + r.w + textx, r.y + texty, SDL_MapRGBA(mainsurface->format, 255, 255, 0, 255), true,
373 				headerCardText.c_str());
374 
375 			ttfPrintTextColor(ttf12, r.x + r.w + bodyx, r.y + bodyy, SDL_MapRGBA(mainsurface->format, 255, 255, 255, 255), true,
376 				displayedText.c_str());
377 		}
378 
379 		if ( cardType == UI_CARD_ACHIEVEMENT )
380 		{
381 			drawWindowFancy(r.x - 2, r.y - 2, r.x + r.w + 2, r.y + r.h + 2);
382 		}
383 
384 		r.w -= padding.w;
385 		r.h -= padding.h;
386 		drawImageScaled(notificationImage, nullptr, &r);
387 
388 		if ( actionFlags & UI_NOTIFICATION_STATISTIC_UPDATE )
389 		{
390 			drawProgressBar(r);
391 		}
392 
393 		if ( drawActionButton(&r) )
394 		{
395 			if ( buttonAction != nullptr )
396 			{
397 				(*buttonAction)();
398 			}
399 		}
400 
401 		if ( temporaryCardHide )
402 		{
403 			animate(animx, anim_ticks, anim_duration, cardWidth, true, mainCardIsHidden);
404 		}
405 		else if ( cardState == UI_CARD_STATE_UPDATE )
406 		{
407 			animate(animx, anim_ticks, anim_duration, cardWidth, true, mainCardIsHidden);
408 			if ( mainCardIsHidden )
409 			{
410 				cardState = UI_CARD_STATE_SHOW;
411 				if ( cardUpdateDisplayMainText )
412 				{
413 					setDisplayedText(mainCardText);
414 				}
415 				else if ( cardUpdateDisplaySecondaryText )
416 				{
417 					setDisplayedText(secondaryCardText);
418 				}
419 				cardUpdateDisplayMainText = false;
420 				cardUpdateDisplaySecondaryText = false;
421 				if ( cardType == UI_CARD_ACHIEVEMENT && pendingStatisticUpdateCurrent != -1 )
422 				{
423 					setStatisticCurrentValue(pendingStatisticUpdateCurrent);
424 					if ( statisticUpdateCurrent >= statisticUpdateMax )
425 					{
426 						this->setHeaderText(std::string("Achievement Unlocked!"));
427 						{
428 							std::string imgName = this->achievementID + std::string(".png");
429 							auto it = achievementImages.find(imgName.c_str());
430 							if ( it != achievementImages.end() )
431 							{
432 								notificationImage = it->second;
433 							}
434 						}
435 					}
436 					pendingStatisticUpdateCurrent = -1;
437 				}
438 			}
439 		}
440 		else
441 		{
442 			bool oldHiddenStatus = mainCardIsHidden;
443 			animate(animx, anim_ticks, anim_duration, cardWidth, mainCardHide, mainCardIsHidden);
444 			if ( mainCardHide && mainCardIsHidden )
445 			{
446 				cardState = actionFlags & UI_NOTIFICATION_REMOVABLE ? UI_CARD_STATE_REMOVED : UI_CARD_STATE_DOCKED;
447 				if ( oldHiddenStatus != mainCardIsHidden && (actionFlags & UI_NOTIFICATION_RESET_TEXT_TO_MAIN_ON_HIDE) )
448 				{
449 					setDisplayedText(mainCardText);
450 				}
451 			}
452 			else
453 			{
454 				if ( ticks - lastInteractedTick > idleTicksToHide )
455 				{
456 					mainCardHide = true;
457 					dockedCardHide = false;
458 				}
459 			}
460 		}
461 	}
animate(int & xout,int & current_ticks,int duration,int width,bool hideElement,bool & isHidden)462 	void animate(int& xout, int& current_ticks, int duration, int width, bool hideElement, bool& isHidden)
463 	{
464 		// scale duration to FPS - tested @ 144hz
465 		double scaledDuration = (duration / (144.f / std::max(1U, fpsLimit)));
466 
467 		double t = current_ticks / static_cast<double>(scaledDuration);
468 		double result = -width * t * t * (3.0f - 2.0f * t); // bezier from 0 to width as t (0-1)
469 		xout = static_cast<int>(floor(result) + width);
470 		isHidden = false;
471 		if ( hideElement )
472 		{
473 			current_ticks = std::max(current_ticks - 1, 0);
474 			if ( current_ticks == 0 )
475 			{
476 				isHidden = true;
477 			}
478 		}
479 		else
480 		{
481 			current_ticks = std::min(current_ticks + 1, static_cast<int>(scaledDuration));
482 		}
483 	}
484 
drawCloseButton(SDL_Rect * src)485 	bool drawCloseButton(SDL_Rect* src)
486 	{
487 		if ( !(actionFlags & ActionFlags::UI_NOTIFICATION_CLOSE) )
488 		{
489 			return false;
490 		}
491 		SDL_Rect closeBtn;
492 		closeBtn.x = xres - 8 - 12 - 8 + animx;
493 		closeBtn.y = src->y - 8 + 4;
494 		closeBtn.w = 16;
495 		closeBtn.h = 16;
496 		if ( !temporaryCardHide && mouseInBounds(closeBtn.x, closeBtn.x + closeBtn.w, closeBtn.y, closeBtn.y + closeBtn.h) )
497 		{
498 			drawDepressed(closeBtn.x, closeBtn.y, closeBtn.x + closeBtn.w, closeBtn.y + closeBtn.h);
499 			if ( mousestatus[SDL_BUTTON_LEFT] )
500 			{
501 				mousestatus[SDL_BUTTON_LEFT] = 0;
502 				ttfPrintText(ttf12, closeBtn.x + 1, closeBtn.y + 2, "x");
503 				mainCardHide = true;
504 				dockedCardHide = false;
505 				lastInteractedTick = ticks;
506 				return true;
507 			}
508 		}
509 		else
510 		{
511 			drawWindow(closeBtn.x, closeBtn.y, closeBtn.x + closeBtn.w, closeBtn.y + closeBtn.h);
512 		}
513 		ttfPrintText(ttf12, closeBtn.x + 1, closeBtn.y + 2, "x");
514 		return false;
515 	}
516 
drawActionButton(SDL_Rect * src)517 	bool drawActionButton(SDL_Rect* src)
518 	{
519 		if ( !(actionFlags & ActionFlags::UI_NOTIFICATION_ACTION_BUTTON) )
520 		{
521 			return false;
522 		}
523 
524 		SDL_Rect actionBtn;
525 		actionBtn.x = src->x + 4 - actionButtonOffsetW / 2;
526 		actionBtn.y = src->y + src->h - 4 - TTF12_HEIGHT + actionButtonOffsetY;
527 		actionBtn.w = src->w - 8 + actionButtonOffsetW;
528 		actionBtn.h = 20;
529 		Uint32 textx = actionBtn.x + (actionBtn.w / 2) - ((TTF12_WIDTH * actionText.length()) / 2) - 3;
530 		if ( actionText.length() == 4 )
531 		{
532 			textx += 1;
533 		}
534 		if ( !temporaryCardHide && mouseInBounds(actionBtn.x, actionBtn.x + actionBtn.w, actionBtn.y, actionBtn.y + actionBtn.h) )
535 		{
536 			//drawDepressed(actionBtn.x, actionBtn.y, actionBtn.x + actionBtn.w, actionBtn.y + actionBtn.h);
537 			drawWindowFancy(actionBtn.x, actionBtn.y, actionBtn.x + actionBtn.w, actionBtn.y + actionBtn.h);
538 			drawRect(&actionBtn, uint32ColorBaronyBlue(*mainsurface), 32);
539 			if ( mousestatus[SDL_BUTTON_LEFT] )
540 			{
541 				mousestatus[SDL_BUTTON_LEFT] = 0;
542 				ttfPrintText(ttf12, textx, actionBtn.y + 5, actionText.c_str());
543 				lastInteractedTick = ticks;
544 				return true;
545 			}
546 		}
547 		else
548 		{
549 			drawWindowFancy(actionBtn.x, actionBtn.y, actionBtn.x + actionBtn.w, actionBtn.y + actionBtn.h);
550 			drawRect(&actionBtn, SDL_MapRGB(mainsurface->format, 255, 255, 255), 32);
551 		}
552 		ttfPrintText(ttf12, textx, actionBtn.y + 5, actionText.c_str());
553 		return false;
554 	}
init()555 	void init()
556 	{
557 		if ( isInit )
558 		{
559 			return;
560 		}
561 		displayedText = mainCardText;
562 		mainCardIsHidden = false;
563 		mainCardHide = false;
564 		isInit = true;
565 		lastInteractedTick = ticks;
566 	}
567 };
568 
569 
570 class UIToastNotificationManager_t
571 {
572 	SDL_Surface* communityLink1 = nullptr;
573 	SDL_Surface* promoLink1 = nullptr;
574 	Uint32 undockTicks = 0;
575 	const Uint32 timeToUndock = 25;
576 	Uint32 lastUndockTick = 0;
577 public:
UIToastNotificationManager_t()578 	UIToastNotificationManager_t() {};
~UIToastNotificationManager_t()579 	~UIToastNotificationManager_t()
580 	{
581 		SDL_FreeSurface(communityLink1);
582 		SDL_FreeSurface(promoLink1);
583 	};
getImage(SDL_Surface * image)584 	SDL_Surface* getImage(SDL_Surface* image)
585 	{
586 		if ( image == nullptr )
587 		{
588 			return communityLink1;
589 		}
590 		return image;
591 	};
592 
593 	bool bIsInit = false;
init()594 	void init()
595 	{
596 		bIsInit = true;
597 		communityLink1 = loadImage("images/system/CommunityLink1.png");
598 		promoLink1 = loadImage("images/system/Promo1.png");
599 	}
600 	void drawNotifications(bool isMoviePlaying, bool beforeFadeout);
601 	void createCommunityNotification();
602 	void createPromoNotification();
603 	void createAchievementNotification(const char* name);
604 	void createStatisticUpdateNotification(const char* name, int currentValue, int maxValue);
605 	void undockAllCards();
606 	void processUndockingCards();
607 	/// @param image nullptr for default barony icon
608 	UIToastNotification* addNotification(SDL_Surface* image);
609 
getNotificationSingle(UIToastNotification::CardType cardType)610 	UIToastNotification* getNotificationSingle(UIToastNotification::CardType cardType)
611 	{
612 		for ( auto& card : allNotifications )
613 		{
614 			if ( card.cardType == cardType )
615 			{
616 				return &card;
617 			}
618 		}
619 		return nullptr;
620 	}
getNotificationAchievementSingle(const char * achName)621 	UIToastNotification* getNotificationAchievementSingle(const char* achName)
622 	{
623 		for ( auto& card : allNotifications )
624 		{
625 			if ( card.cardType == UIToastNotification::CardType::UI_CARD_ACHIEVEMENT )
626 			{
627 				if ( card.matchesAchievementName(achName) )
628 				{
629 					return &card;
630 				}
631 			}
632 		}
633 		return nullptr;
634 	}
635 	std::vector<UIToastNotification> allNotifications;
636 };
637 extern UIToastNotificationManager_t UIToastNotificationManager;
638 
639 void openURLTryWithOverlay(std::string url, bool forceSystemBrowser = false);