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