bauiv1lib.league.rankwindow
UI related to league rank.
1# Released under the MIT License. See LICENSE for details. 2# 3"""UI related to league rank.""" 4# pylint: disable=too-many-lines 5 6from __future__ import annotations 7 8import copy 9import logging 10from typing import TYPE_CHECKING, override 11 12from bauiv1lib.popup import PopupMenu 13import bauiv1 as bui 14 15if TYPE_CHECKING: 16 from typing import Any 17 18 19class LeagueRankWindow(bui.MainWindow): 20 """Window for showing league rank.""" 21 22 def __init__( 23 self, 24 transition: str | None = 'in_right', 25 origin_widget: bui.Widget | None = None, 26 ): 27 # pylint: disable=too-many-statements 28 plus = bui.app.plus 29 assert plus is not None 30 31 bui.set_analytics_screen('League Rank Window') 32 33 self._league_rank_data: dict[str, Any] | None = None 34 35 self._power_ranking_achievements_button: bui.Widget | None = None 36 self._up_to_date_bonus_button: bui.Widget | None = None 37 self._power_ranking_trophies_button: bui.Widget | None = None 38 self._league_title_text: bui.Widget | None = None 39 self._league_text: bui.Widget | None = None 40 self._league_number_text: bui.Widget | None = None 41 self._your_power_ranking_text: bui.Widget | None = None 42 self._loading_spinner: bui.Widget | None = None 43 self._season_ends_text: bui.Widget | None = None 44 self._power_ranking_rank_text: bui.Widget | None = None 45 self._to_ranked_text: bui.Widget | None = None 46 self._trophy_counts_reset_text: bui.Widget | None = None 47 48 assert bui.app.classic is not None 49 uiscale = bui.app.ui_v1.uiscale 50 self._width = 1500 if uiscale is bui.UIScale.SMALL else 1120 51 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 52 self._height = ( 53 1000 54 if uiscale is bui.UIScale.SMALL 55 else 710 if uiscale is bui.UIScale.MEDIUM else 800 56 ) 57 self._r = 'coopSelectWindow' 58 self._rdict = bui.app.lang.get_resource(self._r) 59 # top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 60 61 # self._xoffs = 80.0 if uiscale is bui.UIScale.SMALL else 0 62 self._xoffs = 40 63 64 self._league_url_arg = '' 65 66 self._is_current_season = False 67 self._can_do_more_button = True 68 69 # Do some fancy math to fill all available screen area up to the 70 # size of our backing container. This lets us fit to the exact 71 # screen shape at small ui scale. 72 screensize = bui.get_virtual_screen_size() 73 scale = ( 74 1.3 75 if uiscale is bui.UIScale.SMALL 76 else 0.93 if uiscale is bui.UIScale.MEDIUM else 0.8 77 ) 78 # Calc screen size in our local container space and clamp to a 79 # bit smaller than our container size. 80 target_width = min(self._width - 130, screensize[0] / scale) 81 target_height = min(self._height - 130, screensize[1] / scale) 82 83 # To get top/left coords, go to the center of our window and 84 # offset by half the width/height of our target area. 85 yoffs = 0.5 * self._height + 0.5 * target_height + 30.0 86 87 self._scroll_width = target_width 88 self._scroll_height = target_height - 35 89 scroll_bottom = yoffs - 80 - self._scroll_height 90 91 super().__init__( 92 root_widget=bui.containerwidget( 93 size=(self._width, self._height), 94 stack_offset=( 95 (0, 0) 96 if uiscale is bui.UIScale.SMALL 97 else (0, 10) if uiscale is bui.UIScale.MEDIUM else (0, 0) 98 ), 99 scale=scale, 100 toolbar_visibility=( 101 'menu_minimal' 102 if uiscale is bui.UIScale.SMALL 103 else 'menu_full' 104 ), 105 ), 106 transition=transition, 107 origin_widget=origin_widget, 108 # We're affected by screen size only at small ui-scale. 109 refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, 110 ) 111 112 if uiscale is bui.UIScale.SMALL: 113 self._back_button = bui.get_special_widget('back_button') 114 bui.containerwidget( 115 edit=self._root_widget, on_cancel_call=self.main_window_back 116 ) 117 else: 118 self._back_button = bui.buttonwidget( 119 parent=self._root_widget, 120 position=(75 + x_inset, yoffs - 60), 121 size=(60, 55), 122 scale=1.2, 123 autoselect=True, 124 label=bui.charstr(bui.SpecialChar.BACK), 125 button_type='backSmall', 126 on_activate_call=self.main_window_back, 127 ) 128 bui.containerwidget( 129 edit=self._root_widget, 130 cancel_button=self._back_button, 131 selected_child=self._back_button, 132 ) 133 134 self._title_text = bui.textwidget( 135 parent=self._root_widget, 136 position=( 137 self._width * 0.5, 138 yoffs - (55 if uiscale is bui.UIScale.SMALL else 30), 139 ), 140 size=(0, 0), 141 text=bui.Lstr( 142 resource='league.leagueRankText', 143 fallback_resource='coopSelectWindow.powerRankingText', 144 ), 145 h_align='center', 146 color=bui.app.ui_v1.title_color, 147 scale=1.2 if uiscale is bui.UIScale.SMALL else 1.3, 148 maxwidth=600, 149 v_align='center', 150 ) 151 152 self._scrollwidget = bui.scrollwidget( 153 parent=self._root_widget, 154 highlight=False, 155 size=(self._scroll_width, self._scroll_height), 156 position=( 157 self._width * 0.5 - self._scroll_width * 0.5, 158 scroll_bottom, 159 ), 160 center_small_content=True, 161 center_small_content_horizontally=True, 162 border_opacity=0.4, 163 ) 164 bui.widget(edit=self._scrollwidget, autoselect=True) 165 bui.containerwidget(edit=self._scrollwidget, claims_left_right=True) 166 167 self._last_power_ranking_query_time: float | None = None 168 self._doing_power_ranking_query = False 169 170 self._subcontainer: bui.Widget | None = None 171 self._subcontainerwidth = 1024 172 self._subcontainerheight = 483 173 self._power_ranking_score_widgets: list[bui.Widget] = [] 174 175 self._season_popup_menu: PopupMenu | None = None 176 self._requested_season: str | None = None 177 self._season: str | None = None 178 179 # Take note of our account state; we'll refresh later if this 180 # changes. 181 self._account_state = plus.get_v1_account_state() 182 183 self._refresh() 184 self._restore_state() 185 186 # If we've got cached power-ranking data already, display it. 187 assert bui.app.classic is not None 188 info = bui.app.classic.accounts.get_cached_league_rank_data() 189 if info is not None: 190 self._update_for_league_rank_data(info) 191 192 self._update_timer = bui.AppTimer( 193 1.0, bui.WeakCall(self._update), repeat=True 194 ) 195 self._update(show=info is None) 196 197 @override 198 def get_main_window_state(self) -> bui.MainWindowState: 199 # Support recreating our window for back/refresh purposes. 200 cls = type(self) 201 return bui.BasicMainWindowState( 202 create_call=lambda transition, origin_widget: cls( 203 transition=transition, origin_widget=origin_widget 204 ) 205 ) 206 207 @override 208 def on_main_window_close(self) -> None: 209 self._save_state() 210 211 def _on_achievements_press(self) -> None: 212 from bauiv1lib.achievements import AchievementsWindow 213 214 # Only allow this for all-time or the current season (we 215 # currently don't keep specific achievement data for old 216 # seasons). 217 if self._season == 'a' or self._is_current_season: 218 prab = self._power_ranking_achievements_button 219 assert prab is not None 220 self.main_window_replace(AchievementsWindow(origin_widget=prab)) 221 else: 222 bui.screenmessage( 223 bui.Lstr( 224 resource='achievementsUnavailableForOldSeasonsText', 225 fallback_resource='unavailableText', 226 ), 227 color=(1, 0, 0), 228 ) 229 bui.getsound('error').play() 230 231 def _on_activity_mult_press(self) -> None: 232 from bauiv1lib import confirm 233 234 plus = bui.app.plus 235 assert plus is not None 236 237 txt = bui.Lstr( 238 resource=( 239 'coopSelectWindow.activenessAllTimeInfoText' 240 if self._season == 'a' 241 else 'coopSelectWindow.activenessInfoText' 242 ), 243 subs=[ 244 ( 245 '${MAX}', 246 str( 247 plus.get_v1_account_misc_read_val('activenessMax', 1.0) 248 ), 249 ) 250 ], 251 ) 252 confirm.ConfirmWindow( 253 txt, 254 cancel_button=False, 255 width=460, 256 height=150, 257 origin_widget=self._activity_mult_button, 258 ) 259 260 def _on_up_to_date_bonus_press(self) -> None: 261 from bauiv1lib import confirm 262 263 plus = bui.app.plus 264 assert plus is not None 265 266 txt = bui.Lstr( 267 resource='league.upToDateBonusDescriptionText', 268 subs=[ 269 ( 270 '${PERCENT}', 271 str( 272 plus.get_v1_account_misc_read_val( 273 'proPowerRankingBoost', 10 274 ) 275 ), 276 ), 277 ], 278 ) 279 confirm.ConfirmWindow( 280 txt, 281 cancel_button=False, 282 width=460, 283 height=130, 284 origin_widget=self._up_to_date_bonus_button, 285 ) 286 287 def _on_trophies_press(self) -> None: 288 from bauiv1lib.trophies import TrophiesWindow 289 290 info = self._league_rank_data 291 if info is not None: 292 prtb = self._power_ranking_trophies_button 293 assert prtb is not None 294 TrophiesWindow( 295 position=prtb.get_screen_space_center(), 296 data=info, 297 ) 298 else: 299 bui.getsound('error').play() 300 301 def _on_power_ranking_query_response( 302 self, data: dict[str, Any] | None 303 ) -> None: 304 self._doing_power_ranking_query = False 305 306 # Important: *only* cache this if we requested the current 307 # season. 308 if data is not None and data.get('s', None) is None: 309 assert bui.app.classic is not None 310 bui.app.classic.accounts.cache_league_rank_data(data) 311 312 # Always store a copy locally though (even for other seasons). 313 self._league_rank_data = copy.deepcopy(data) 314 self._update_for_league_rank_data(data) 315 316 def _restore_state(self) -> None: 317 pass 318 319 def _update(self, show: bool = False) -> None: 320 plus = bui.app.plus 321 assert plus is not None 322 323 cur_time = bui.apptime() 324 325 # If our account state has changed, refresh our UI. 326 account_state = plus.get_v1_account_state() 327 if account_state != self._account_state: 328 self._account_state = account_state 329 self._save_state() 330 self._refresh() 331 332 # And power ranking too. 333 if not self._doing_power_ranking_query: 334 self._last_power_ranking_query_time = None 335 336 # Send off a new power-ranking query if its been long enough or 337 # our requested season has changed or whatnot. 338 if not self._doing_power_ranking_query and ( 339 self._last_power_ranking_query_time is None 340 or cur_time - self._last_power_ranking_query_time > 30.0 341 ): 342 try: 343 if show: 344 bui.textwidget(edit=self._league_title_text, text='') 345 bui.textwidget(edit=self._league_text, text='') 346 bui.textwidget(edit=self._league_number_text, text='') 347 bui.textwidget(edit=self._your_power_ranking_text, text='') 348 bui.spinnerwidget(edit=self._loading_spinner, visible=True) 349 bui.textwidget(edit=self._to_ranked_text, text='') 350 bui.textwidget(edit=self._power_ranking_rank_text, text='') 351 bui.textwidget(edit=self._season_ends_text, text='') 352 bui.textwidget(edit=self._trophy_counts_reset_text, text='') 353 except Exception: 354 logging.exception('Error showing updated rank info.') 355 356 self._last_power_ranking_query_time = cur_time 357 self._doing_power_ranking_query = True 358 plus.power_ranking_query( 359 season=self._requested_season, 360 callback=bui.WeakCall(self._on_power_ranking_query_response), 361 ) 362 363 def _refresh(self) -> None: 364 # pylint: disable=too-many-statements 365 366 plus = bui.app.plus 367 assert plus is not None 368 369 # (Re)create the sub-container if need be. 370 if self._subcontainer is not None: 371 self._subcontainer.delete() 372 self._subcontainer = bui.containerwidget( 373 parent=self._scrollwidget, 374 size=(self._subcontainerwidth, self._subcontainerheight), 375 background=False, 376 ) 377 378 w_parent = self._subcontainer 379 v = self._subcontainerheight - 20 380 381 v -= 0 382 383 h2 = 80 384 v2 = v - 60 385 worth_color = (0.6, 0.6, 0.65) 386 tally_color = (0.5, 0.6, 0.8) 387 spc = 43 388 389 h_offs_tally = 150 390 tally_maxwidth = 120 391 v2 -= 70 392 393 bui.textwidget( 394 parent=w_parent, 395 position=(self._xoffs + h2 - 60, v2 + 106), 396 size=(0, 0), 397 flatness=1.0, 398 shadow=0.0, 399 text=bui.Lstr(resource='coopSelectWindow.pointsText'), 400 h_align='left', 401 v_align='center', 402 scale=0.8, 403 color=(1, 1, 1, 0.3), 404 maxwidth=200, 405 ) 406 407 self._power_ranking_achievements_button = bui.buttonwidget( 408 parent=w_parent, 409 position=(self._xoffs + h2 - 60, v2 + 10), 410 size=(200, 80), 411 icon=bui.gettexture('achievementsIcon'), 412 autoselect=True, 413 on_activate_call=bui.WeakCall(self._on_achievements_press), 414 up_widget=self._back_button, 415 left_widget=self._back_button, 416 color=(0.5, 0.5, 0.6), 417 textcolor=(0.7, 0.7, 0.8), 418 label='', 419 ) 420 421 self._power_ranking_achievement_total_text = bui.textwidget( 422 parent=w_parent, 423 position=(self._xoffs + h2 + h_offs_tally, v2 + 45), 424 size=(0, 0), 425 flatness=1.0, 426 shadow=0.0, 427 text='-', 428 h_align='left', 429 v_align='center', 430 scale=0.8, 431 color=tally_color, 432 maxwidth=tally_maxwidth, 433 ) 434 435 v2 -= 80 436 437 self._power_ranking_trophies_button = bui.buttonwidget( 438 parent=w_parent, 439 position=(self._xoffs + h2 - 60, v2 + 10), 440 size=(200, 80), 441 icon=bui.gettexture('medalSilver'), 442 autoselect=True, 443 on_activate_call=bui.WeakCall(self._on_trophies_press), 444 left_widget=self._back_button, 445 color=(0.5, 0.5, 0.6), 446 textcolor=(0.7, 0.7, 0.8), 447 label='', 448 ) 449 self._power_ranking_trophies_total_text = bui.textwidget( 450 parent=w_parent, 451 position=(self._xoffs + h2 + h_offs_tally, v2 + 45), 452 size=(0, 0), 453 flatness=1.0, 454 shadow=0.0, 455 text='-', 456 h_align='left', 457 v_align='center', 458 scale=0.8, 459 color=tally_color, 460 maxwidth=tally_maxwidth, 461 ) 462 463 v2 -= 100 464 465 bui.textwidget( 466 parent=w_parent, 467 position=(self._xoffs + h2 - 60, v2 + 86), 468 size=(0, 0), 469 flatness=1.0, 470 shadow=0.0, 471 text=bui.Lstr(resource='coopSelectWindow.multipliersText'), 472 h_align='left', 473 v_align='center', 474 scale=0.8, 475 color=(1, 1, 1, 0.3), 476 maxwidth=200, 477 ) 478 479 self._activity_mult_button: bui.Widget | None 480 if plus.get_v1_account_misc_read_val('act', False): 481 self._activity_mult_button = bui.buttonwidget( 482 parent=w_parent, 483 position=(self._xoffs + h2 - 60, v2 + 10), 484 size=(200, 60), 485 icon=bui.gettexture('heart'), 486 icon_color=(0.5, 0, 0.5), 487 label=bui.Lstr(resource='coopSelectWindow.activityText'), 488 autoselect=True, 489 on_activate_call=bui.WeakCall(self._on_activity_mult_press), 490 left_widget=self._back_button, 491 color=(0.5, 0.5, 0.6), 492 textcolor=(0.7, 0.7, 0.8), 493 ) 494 495 self._activity_mult_text = bui.textwidget( 496 parent=w_parent, 497 position=(self._xoffs + h2 + h_offs_tally, v2 + 40), 498 size=(0, 0), 499 flatness=1.0, 500 shadow=0.0, 501 text='-', 502 h_align='left', 503 v_align='center', 504 scale=0.8, 505 color=tally_color, 506 maxwidth=tally_maxwidth, 507 ) 508 v2 -= 65 509 else: 510 self._activity_mult_button = None 511 512 self._up_to_date_bonus_button = bui.buttonwidget( 513 parent=w_parent, 514 position=(self._xoffs + h2 - 60, v2 + 10), 515 size=(200, 60), 516 icon=bui.gettexture('logo'), 517 icon_color=(0.3, 0, 0.3), 518 label=bui.Lstr(resource='league.upToDateBonusText'), 519 autoselect=True, 520 on_activate_call=bui.WeakCall(self._on_up_to_date_bonus_press), 521 left_widget=self._back_button, 522 color=(0.5, 0.5, 0.6), 523 textcolor=(0.7, 0.7, 0.8), 524 ) 525 526 self._up_to_date_bonus_text = bui.textwidget( 527 parent=w_parent, 528 position=(self._xoffs + h2 + h_offs_tally, v2 + 40), 529 size=(0, 0), 530 flatness=1.0, 531 shadow=0.0, 532 text='-', 533 h_align='left', 534 v_align='center', 535 scale=0.8, 536 color=tally_color, 537 maxwidth=tally_maxwidth, 538 ) 539 v2 -= 30 540 541 v2 -= spc 542 bui.textwidget( 543 parent=w_parent, 544 position=(self._xoffs + h2 + h_offs_tally - 10 - 40, v2 + 35), 545 size=(0, 0), 546 flatness=1.0, 547 shadow=0.0, 548 text=bui.Lstr(resource='finalScoreText'), 549 h_align='right', 550 v_align='center', 551 scale=0.9, 552 color=worth_color, 553 maxwidth=150, 554 ) 555 self._power_ranking_total_text = bui.textwidget( 556 parent=w_parent, 557 position=(self._xoffs + h2 + h_offs_tally - 40, v2 + 35), 558 size=(0, 0), 559 flatness=1.0, 560 shadow=0.0, 561 text='-', 562 h_align='left', 563 v_align='center', 564 scale=0.9, 565 color=tally_color, 566 maxwidth=tally_maxwidth, 567 ) 568 569 self._season_show_text = bui.textwidget( 570 parent=w_parent, 571 position=(self._xoffs + 390 - 15, v - 20), 572 size=(0, 0), 573 color=(0.6, 0.6, 0.7), 574 maxwidth=200, 575 text=bui.Lstr(resource='showText'), 576 h_align='right', 577 v_align='center', 578 scale=0.8, 579 shadow=0, 580 flatness=1.0, 581 ) 582 583 self._league_title_text = bui.textwidget( 584 parent=w_parent, 585 position=(self._xoffs + 470, v - 97), 586 size=(0, 0), 587 color=(0.6, 0.6, 0.7), 588 maxwidth=230, 589 text='', 590 h_align='center', 591 v_align='center', 592 scale=0.9, 593 shadow=0, 594 flatness=1.0, 595 ) 596 597 self._league_text_scale = 1.8 598 self._league_text_maxwidth = 210 599 self._league_text = bui.textwidget( 600 parent=w_parent, 601 position=(self._xoffs + 470, v - 140), 602 size=(0, 0), 603 color=(1, 1, 1), 604 maxwidth=self._league_text_maxwidth, 605 text='-', 606 h_align='center', 607 v_align='center', 608 scale=self._league_text_scale, 609 shadow=1.0, 610 flatness=1.0, 611 ) 612 self._league_number_base_pos = (470, v - 140) 613 self._league_number_text = bui.textwidget( 614 parent=w_parent, 615 position=(self._xoffs + 470, v - 140), 616 size=(0, 0), 617 color=(1, 1, 1), 618 maxwidth=100, 619 text='', 620 h_align='left', 621 v_align='center', 622 scale=0.8, 623 shadow=1.0, 624 flatness=1.0, 625 ) 626 627 self._loading_spinner = bui.spinnerwidget( 628 parent=w_parent, 629 position=( 630 self._subcontainerwidth * 0.5, 631 self._subcontainerheight * 0.5, 632 ), 633 style='bomb', 634 size=64, 635 ) 636 self._your_power_ranking_text = bui.textwidget( 637 parent=w_parent, 638 position=(self._xoffs + 470, v - 142 - 70), 639 size=(0, 0), 640 color=(0.6, 0.6, 0.7), 641 maxwidth=230, 642 text='', 643 h_align='center', 644 v_align='center', 645 scale=0.9, 646 shadow=0, 647 flatness=1.0, 648 ) 649 650 self._to_ranked_text = bui.textwidget( 651 parent=w_parent, 652 position=(self._xoffs + 470, v - 250 - 70), 653 size=(0, 0), 654 color=(0.6, 0.6, 0.7), 655 maxwidth=230, 656 text='', 657 h_align='center', 658 v_align='center', 659 scale=0.8, 660 shadow=0, 661 flatness=1.0, 662 ) 663 664 self._power_ranking_rank_text = bui.textwidget( 665 parent=w_parent, 666 position=(self._xoffs + 473, v - 210 - 70), 667 size=(0, 0), 668 big=False, 669 text='-', 670 h_align='center', 671 v_align='center', 672 scale=1.0, 673 ) 674 675 self._season_ends_text = bui.textwidget( 676 parent=w_parent, 677 position=(self._xoffs + 470, v - 380), 678 size=(0, 0), 679 color=(0.6, 0.6, 0.6), 680 maxwidth=230, 681 text='', 682 h_align='center', 683 v_align='center', 684 scale=0.9, 685 shadow=0, 686 flatness=1.0, 687 ) 688 self._trophy_counts_reset_text = bui.textwidget( 689 parent=w_parent, 690 position=(self._xoffs + 470, v - 410), 691 size=(0, 0), 692 color=(0.5, 0.5, 0.5), 693 maxwidth=230, 694 text='Trophy counts will reset next season.', 695 h_align='center', 696 v_align='center', 697 scale=0.8, 698 shadow=0, 699 flatness=1.0, 700 ) 701 702 self._power_ranking_score_widgets = [] 703 704 self._power_ranking_score_v = v - 56 705 706 h = 707 707 v -= 451 708 709 self._see_more_button = bui.buttonwidget( 710 parent=w_parent, 711 label=self._rdict.seeMoreText, 712 position=(self._xoffs + h, v), 713 color=(0.5, 0.5, 0.6), 714 textcolor=(0.7, 0.7, 0.8), 715 size=(230, 60), 716 autoselect=True, 717 on_activate_call=bui.WeakCall(self._on_more_press), 718 ) 719 720 def _on_more_press(self) -> None: 721 plus = bui.app.plus 722 assert plus is not None 723 724 our_login_id = plus.get_v1_account_public_login_id() 725 if not self._can_do_more_button or our_login_id is None: 726 bui.getsound('error').play() 727 bui.screenmessage( 728 bui.Lstr(resource='unavailableText'), color=(1, 0, 0) 729 ) 730 return 731 if self._season is None: 732 season_str = '' 733 else: 734 season_str = '&season=' + ( 735 'all_time' if self._season == 'a' else self._season 736 ) 737 if self._league_url_arg != '': 738 league_str = '&league=' + self._league_url_arg 739 else: 740 league_str = '' 741 bui.open_url( 742 plus.get_master_server_address() 743 + '/highscores?list=powerRankings&v=2' 744 + league_str 745 + season_str 746 + '&player=' 747 + our_login_id 748 ) 749 750 def _update_for_league_rank_data(self, data: dict[str, Any] | None) -> None: 751 # pylint: disable=too-many-statements 752 # pylint: disable=too-many-branches 753 # pylint: disable=too-many-locals 754 if not self._root_widget: 755 return 756 plus = bui.app.plus 757 uiscale = bui.app.ui_v1.uiscale 758 assert plus is not None 759 assert bui.app.classic is not None 760 accounts = bui.app.classic.accounts 761 in_top = data is not None and data['rank'] is not None 762 eq_text = self._rdict.powerRankingPointsEqualsText 763 pts_txt = self._rdict.powerRankingPointsText 764 num_text = bui.Lstr(resource='numberText').evaluate() 765 do_percent = False 766 finished_season_unranked = False 767 self._can_do_more_button = True 768 extra_text = '' 769 if plus.get_v1_account_state() != 'signed_in': 770 status_text = ( 771 '(' + bui.Lstr(resource='notSignedInText').evaluate() + ')' 772 ) 773 elif in_top: 774 assert data is not None 775 status_text = num_text.replace('${NUMBER}', str(data['rank'])) 776 elif data is not None: 777 try: 778 # Handle old seasons where we didn't wind up ranked at 779 # the end. 780 if not data['scores']: 781 status_text = ( 782 self._rdict.powerRankingFinishedSeasonUnrankedText 783 ) 784 extra_text = '' 785 finished_season_unranked = True 786 self._can_do_more_button = False 787 else: 788 our_points = accounts.get_league_rank_points(data) 789 progress = float(our_points) / max(1, data['scores'][-1][1]) 790 status_text = str(int(progress * 100.0)) + '%' 791 extra_text = ( 792 '\n' 793 + self._rdict.powerRankingPointsToRankedText.replace( 794 '${CURRENT}', str(our_points) 795 ).replace('${REMAINING}', str(data['scores'][-1][1])) 796 ) 797 do_percent = True 798 except Exception: 799 logging.exception('Error updating power ranking.') 800 status_text = self._rdict.powerRankingNotInTopText.replace( 801 '${NUMBER}', str(data['listSize']) 802 ) 803 extra_text = '' 804 else: 805 status_text = '-' 806 807 self._season = data['s'] if data is not None else None 808 809 v = self._subcontainerheight - 20 810 popup_was_selected = False 811 if self._season_popup_menu is not None: 812 btn = self._season_popup_menu.get_button() 813 assert self._subcontainer 814 if self._subcontainer.get_selected_child() == btn: 815 popup_was_selected = True 816 btn.delete() 817 season_choices = [] 818 season_choices_display = [] 819 did_first = False 820 self._is_current_season = False 821 if data is not None: 822 # Build our list of seasons we have available. 823 for ssn in data['sl']: 824 season_choices.append(ssn) 825 if ssn != 'a' and not did_first: 826 season_choices_display.append( 827 bui.Lstr( 828 resource='league.currentSeasonText', 829 subs=[('${NUMBER}', ssn)], 830 ) 831 ) 832 did_first = True 833 834 # If we either did not specify a season or specified 835 # the first, we're looking at the current. 836 if self._season in [ssn, None]: 837 self._is_current_season = True 838 elif ssn == 'a': 839 season_choices_display.append( 840 bui.Lstr(resource='league.allTimeText') 841 ) 842 else: 843 season_choices_display.append( 844 bui.Lstr( 845 resource='league.seasonText', 846 subs=[('${NUMBER}', ssn)], 847 ) 848 ) 849 assert self._subcontainer 850 self._season_popup_menu = PopupMenu( 851 parent=self._subcontainer, 852 position=(self._xoffs + 390, v - 45), 853 width=150, 854 button_size=(200, 50), 855 choices=season_choices, 856 on_value_change_call=bui.WeakCall(self._on_season_change), 857 choices_display=season_choices_display, 858 current_choice=self._season, 859 ) 860 if popup_was_selected: 861 bui.containerwidget( 862 edit=self._subcontainer, 863 selected_child=self._season_popup_menu.get_button(), 864 ) 865 bui.widget(edit=self._see_more_button, show_buffer_bottom=100) 866 bui.widget( 867 edit=self._season_popup_menu.get_button(), 868 up_widget=self._back_button, 869 ) 870 if uiscale is not bui.UIScale.SMALL: 871 bui.widget( 872 edit=self._back_button, 873 down_widget=self._power_ranking_achievements_button, 874 right_widget=self._season_popup_menu.get_button(), 875 ) 876 877 bui.textwidget( 878 edit=self._league_title_text, 879 text=( 880 '' 881 if self._season == 'a' 882 else bui.Lstr(resource='league.leagueText') 883 ), 884 ) 885 886 if data is None: 887 lname = '' 888 lnum = '' 889 lcolor = (1, 1, 1) 890 self._league_url_arg = '' 891 elif self._season == 'a': 892 lname = bui.Lstr(resource='league.allTimeText').evaluate() 893 lnum = '' 894 lcolor = (1, 1, 1) 895 self._league_url_arg = '' 896 else: 897 lnum = ('[' + str(data['l']['i']) + ']') if data['l']['i2'] else '' 898 lname = bui.Lstr( 899 translate=('leagueNames', data['l']['n']) 900 ).evaluate() 901 lcolor = data['l']['c'] 902 self._league_url_arg = ( 903 data['l']['n'] + '_' + str(data['l']['i']) 904 ).lower() 905 906 to_end_string: bui.Lstr | str 907 if data is None or self._season == 'a' or data['se'] is None: 908 to_end_string = '' 909 show_season_end = False 910 else: 911 show_season_end = True 912 days_to_end = data['se'][0] 913 minutes_to_end = data['se'][1] 914 if days_to_end > 0: 915 to_end_string = bui.Lstr( 916 resource='league.seasonEndsDaysText', 917 subs=[('${NUMBER}', str(days_to_end))], 918 ) 919 elif days_to_end == 0 and minutes_to_end >= 60: 920 to_end_string = bui.Lstr( 921 resource='league.seasonEndsHoursText', 922 subs=[('${NUMBER}', str(minutes_to_end // 60))], 923 ) 924 elif days_to_end == 0 and minutes_to_end >= 0: 925 to_end_string = bui.Lstr( 926 resource='league.seasonEndsMinutesText', 927 subs=[('${NUMBER}', str(minutes_to_end))], 928 ) 929 else: 930 to_end_string = bui.Lstr( 931 resource='league.seasonEndedDaysAgoText', 932 subs=[('${NUMBER}', str(-(days_to_end + 1)))], 933 ) 934 935 bui.textwidget(edit=self._season_ends_text, text=to_end_string) 936 bui.textwidget( 937 edit=self._trophy_counts_reset_text, 938 text=( 939 bui.Lstr(resource='league.trophyCountsResetText') 940 if self._is_current_season and show_season_end 941 else '' 942 ), 943 ) 944 945 bui.textwidget(edit=self._league_text, text=lname, color=lcolor) 946 l_text_width = min( 947 self._league_text_maxwidth, 948 bui.get_string_width(lname, suppress_warning=True) 949 * self._league_text_scale, 950 ) 951 bui.textwidget( 952 edit=self._league_number_text, 953 text=lnum, 954 color=lcolor, 955 position=( 956 self._xoffs 957 + self._league_number_base_pos[0] 958 + l_text_width * 0.5 959 + 8, 960 self._league_number_base_pos[1] + 10, 961 ), 962 ) 963 bui.textwidget( 964 edit=self._to_ranked_text, 965 text=( 966 bui.Lstr(resource='coopSelectWindow.toRankedText').evaluate() 967 + '' 968 + extra_text 969 if do_percent 970 else '' 971 ), 972 ) 973 974 bui.textwidget( 975 edit=self._your_power_ranking_text, 976 text=( 977 bui.Lstr( 978 resource='rankText', 979 fallback_resource='coopSelectWindow.yourPowerRankingText', 980 ) 981 if (not do_percent) 982 else '' 983 ), 984 ) 985 bui.spinnerwidget(edit=self._loading_spinner, visible=False) 986 987 bui.textwidget( 988 edit=self._power_ranking_rank_text, 989 position=(self._xoffs + 473, v - 70 - (170 if do_percent else 220)), 990 text=status_text, 991 big=(in_top or do_percent), 992 scale=( 993 3.0 994 if (in_top or do_percent) 995 else 0.7 if finished_season_unranked else 1.0 996 ), 997 ) 998 999 if self._activity_mult_button is not None: 1000 if data is None or data['act'] is None: 1001 bui.buttonwidget( 1002 edit=self._activity_mult_button, 1003 textcolor=(0.7, 0.7, 0.8, 0.5), 1004 icon_color=(0.5, 0, 0.5, 0.3), 1005 ) 1006 bui.textwidget(edit=self._activity_mult_text, text=' -') 1007 else: 1008 bui.buttonwidget( 1009 edit=self._activity_mult_button, 1010 textcolor=(0.7, 0.7, 0.8, 1.0), 1011 icon_color=(0.5, 0, 0.5, 1.0), 1012 ) 1013 bui.textwidget( 1014 edit=self._activity_mult_text, 1015 text=f'x {data['act']:.2f}', 1016 ) 1017 1018 # This used to be a bonus for 'BombSquad Pro' holders, but since 1019 # we're transitioning away from that it is now a bonus for 1020 # everyone running a recent-ish version of the game. 1021 1022 have_up_to_date_bonus = data is not None 1023 pro_mult = ( 1024 1.0 1025 + float( 1026 plus.get_v1_account_misc_read_val('proPowerRankingBoost', 0.0) 1027 ) 1028 * 0.01 1029 ) 1030 bui.textwidget( 1031 edit=self._up_to_date_bonus_text, 1032 text=( 1033 ' -' 1034 if (data is None or not have_up_to_date_bonus) 1035 else f'x {pro_mult:.2f}' 1036 ), 1037 ) 1038 bui.buttonwidget( 1039 edit=self._up_to_date_bonus_button, 1040 textcolor=(0.7, 0.7, 0.8, (1.0 if have_up_to_date_bonus else 0.5)), 1041 icon_color=( 1042 (0.5, 0, 0.5) if have_up_to_date_bonus else (0.5, 0, 0.5, 0.2) 1043 ), 1044 ) 1045 bui.buttonwidget( 1046 edit=self._power_ranking_achievements_button, 1047 label=('' if data is None else str(data['a']) + ' ') 1048 + bui.Lstr(resource='achievementsText').evaluate(), 1049 ) 1050 1051 # For the achievement value, use the number they gave us for 1052 # non-current seasons; otherwise calc our own. 1053 total_ach_value = 0 1054 for ach in bui.app.classic.ach.achievements: 1055 if ach.complete: 1056 total_ach_value += ach.power_ranking_value 1057 if self._season != 'a' and not self._is_current_season: 1058 if data is not None and 'at' in data: 1059 total_ach_value = data['at'] 1060 1061 bui.textwidget( 1062 edit=self._power_ranking_achievement_total_text, 1063 text=( 1064 '-' 1065 if data is None 1066 else ('+ ' + pts_txt.replace('${NUMBER}', str(total_ach_value))) 1067 ), 1068 ) 1069 1070 total_trophies_count = accounts.get_league_rank_points( 1071 data, 'trophyCount' 1072 ) 1073 total_trophies_value = accounts.get_league_rank_points(data, 'trophies') 1074 bui.buttonwidget( 1075 edit=self._power_ranking_trophies_button, 1076 label=('' if data is None else str(total_trophies_count) + ' ') 1077 + bui.Lstr(resource='trophiesText').evaluate(), 1078 ) 1079 bui.textwidget( 1080 edit=self._power_ranking_trophies_total_text, 1081 text=( 1082 '-' 1083 if data is None 1084 else ( 1085 '+ ' 1086 + pts_txt.replace('${NUMBER}', str(total_trophies_value)) 1087 ) 1088 ), 1089 ) 1090 1091 bui.textwidget( 1092 edit=self._power_ranking_total_text, 1093 text=( 1094 '-' 1095 if data is None 1096 else eq_text.replace( 1097 '${NUMBER}', str(accounts.get_league_rank_points(data)) 1098 ) 1099 ), 1100 ) 1101 for widget in self._power_ranking_score_widgets: 1102 widget.delete() 1103 self._power_ranking_score_widgets = [] 1104 1105 scores = data['scores'] if data is not None else [] 1106 tally_color = (0.5, 0.6, 0.8) 1107 w_parent = self._subcontainer 1108 v2 = self._power_ranking_score_v 1109 1110 for score in scores: 1111 h2 = 680 1112 is_us = score[3] 1113 self._power_ranking_score_widgets.append( 1114 bui.textwidget( 1115 parent=w_parent, 1116 position=(self._xoffs + h2 - 20, v2), 1117 size=(0, 0), 1118 color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7), 1119 maxwidth=40, 1120 flatness=1.0, 1121 shadow=0.0, 1122 text=num_text.replace('${NUMBER}', str(score[0])), 1123 h_align='right', 1124 v_align='center', 1125 scale=0.5, 1126 ) 1127 ) 1128 self._power_ranking_score_widgets.append( 1129 bui.textwidget( 1130 parent=w_parent, 1131 position=(self._xoffs + h2 + 20, v2), 1132 size=(0, 0), 1133 color=(1, 1, 1) if is_us else tally_color, 1134 maxwidth=60, 1135 text=str(score[1]), 1136 flatness=1.0, 1137 shadow=0.0, 1138 h_align='center', 1139 v_align='center', 1140 scale=0.7, 1141 ) 1142 ) 1143 txt = bui.textwidget( 1144 parent=w_parent, 1145 position=(self._xoffs + h2 + 60, v2 - (28 * 0.5) / 0.9), 1146 size=(210 / 0.9, 28), 1147 color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6), 1148 maxwidth=210, 1149 flatness=1.0, 1150 shadow=0.0, 1151 autoselect=True, 1152 selectable=True, 1153 click_activate=True, 1154 text=score[2], 1155 h_align='left', 1156 v_align='center', 1157 scale=0.9, 1158 ) 1159 self._power_ranking_score_widgets.append(txt) 1160 bui.textwidget( 1161 edit=txt, 1162 on_activate_call=bui.Call( 1163 self._show_account_info, score[4], txt 1164 ), 1165 ) 1166 assert self._season_popup_menu is not None 1167 bui.widget( 1168 edit=txt, left_widget=self._season_popup_menu.get_button() 1169 ) 1170 v2 -= 28 1171 1172 def _show_account_info( 1173 self, account_id: str, textwidget: bui.Widget 1174 ) -> None: 1175 from bauiv1lib.account.viewer import AccountViewerWindow 1176 1177 bui.getsound('swish').play() 1178 AccountViewerWindow( 1179 account_id=account_id, position=textwidget.get_screen_space_center() 1180 ) 1181 1182 def _on_season_change(self, value: str) -> None: 1183 self._requested_season = value 1184 self._last_power_ranking_query_time = None # Update asap. 1185 self._update(show=True) 1186 1187 def _save_state(self) -> None: 1188 pass
class
LeagueRankWindow(bauiv1._uitypes.MainWindow):
20class LeagueRankWindow(bui.MainWindow): 21 """Window for showing league rank.""" 22 23 def __init__( 24 self, 25 transition: str | None = 'in_right', 26 origin_widget: bui.Widget | None = None, 27 ): 28 # pylint: disable=too-many-statements 29 plus = bui.app.plus 30 assert plus is not None 31 32 bui.set_analytics_screen('League Rank Window') 33 34 self._league_rank_data: dict[str, Any] | None = None 35 36 self._power_ranking_achievements_button: bui.Widget | None = None 37 self._up_to_date_bonus_button: bui.Widget | None = None 38 self._power_ranking_trophies_button: bui.Widget | None = None 39 self._league_title_text: bui.Widget | None = None 40 self._league_text: bui.Widget | None = None 41 self._league_number_text: bui.Widget | None = None 42 self._your_power_ranking_text: bui.Widget | None = None 43 self._loading_spinner: bui.Widget | None = None 44 self._season_ends_text: bui.Widget | None = None 45 self._power_ranking_rank_text: bui.Widget | None = None 46 self._to_ranked_text: bui.Widget | None = None 47 self._trophy_counts_reset_text: bui.Widget | None = None 48 49 assert bui.app.classic is not None 50 uiscale = bui.app.ui_v1.uiscale 51 self._width = 1500 if uiscale is bui.UIScale.SMALL else 1120 52 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 53 self._height = ( 54 1000 55 if uiscale is bui.UIScale.SMALL 56 else 710 if uiscale is bui.UIScale.MEDIUM else 800 57 ) 58 self._r = 'coopSelectWindow' 59 self._rdict = bui.app.lang.get_resource(self._r) 60 # top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 61 62 # self._xoffs = 80.0 if uiscale is bui.UIScale.SMALL else 0 63 self._xoffs = 40 64 65 self._league_url_arg = '' 66 67 self._is_current_season = False 68 self._can_do_more_button = True 69 70 # Do some fancy math to fill all available screen area up to the 71 # size of our backing container. This lets us fit to the exact 72 # screen shape at small ui scale. 73 screensize = bui.get_virtual_screen_size() 74 scale = ( 75 1.3 76 if uiscale is bui.UIScale.SMALL 77 else 0.93 if uiscale is bui.UIScale.MEDIUM else 0.8 78 ) 79 # Calc screen size in our local container space and clamp to a 80 # bit smaller than our container size. 81 target_width = min(self._width - 130, screensize[0] / scale) 82 target_height = min(self._height - 130, screensize[1] / scale) 83 84 # To get top/left coords, go to the center of our window and 85 # offset by half the width/height of our target area. 86 yoffs = 0.5 * self._height + 0.5 * target_height + 30.0 87 88 self._scroll_width = target_width 89 self._scroll_height = target_height - 35 90 scroll_bottom = yoffs - 80 - self._scroll_height 91 92 super().__init__( 93 root_widget=bui.containerwidget( 94 size=(self._width, self._height), 95 stack_offset=( 96 (0, 0) 97 if uiscale is bui.UIScale.SMALL 98 else (0, 10) if uiscale is bui.UIScale.MEDIUM else (0, 0) 99 ), 100 scale=scale, 101 toolbar_visibility=( 102 'menu_minimal' 103 if uiscale is bui.UIScale.SMALL 104 else 'menu_full' 105 ), 106 ), 107 transition=transition, 108 origin_widget=origin_widget, 109 # We're affected by screen size only at small ui-scale. 110 refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, 111 ) 112 113 if uiscale is bui.UIScale.SMALL: 114 self._back_button = bui.get_special_widget('back_button') 115 bui.containerwidget( 116 edit=self._root_widget, on_cancel_call=self.main_window_back 117 ) 118 else: 119 self._back_button = bui.buttonwidget( 120 parent=self._root_widget, 121 position=(75 + x_inset, yoffs - 60), 122 size=(60, 55), 123 scale=1.2, 124 autoselect=True, 125 label=bui.charstr(bui.SpecialChar.BACK), 126 button_type='backSmall', 127 on_activate_call=self.main_window_back, 128 ) 129 bui.containerwidget( 130 edit=self._root_widget, 131 cancel_button=self._back_button, 132 selected_child=self._back_button, 133 ) 134 135 self._title_text = bui.textwidget( 136 parent=self._root_widget, 137 position=( 138 self._width * 0.5, 139 yoffs - (55 if uiscale is bui.UIScale.SMALL else 30), 140 ), 141 size=(0, 0), 142 text=bui.Lstr( 143 resource='league.leagueRankText', 144 fallback_resource='coopSelectWindow.powerRankingText', 145 ), 146 h_align='center', 147 color=bui.app.ui_v1.title_color, 148 scale=1.2 if uiscale is bui.UIScale.SMALL else 1.3, 149 maxwidth=600, 150 v_align='center', 151 ) 152 153 self._scrollwidget = bui.scrollwidget( 154 parent=self._root_widget, 155 highlight=False, 156 size=(self._scroll_width, self._scroll_height), 157 position=( 158 self._width * 0.5 - self._scroll_width * 0.5, 159 scroll_bottom, 160 ), 161 center_small_content=True, 162 center_small_content_horizontally=True, 163 border_opacity=0.4, 164 ) 165 bui.widget(edit=self._scrollwidget, autoselect=True) 166 bui.containerwidget(edit=self._scrollwidget, claims_left_right=True) 167 168 self._last_power_ranking_query_time: float | None = None 169 self._doing_power_ranking_query = False 170 171 self._subcontainer: bui.Widget | None = None 172 self._subcontainerwidth = 1024 173 self._subcontainerheight = 483 174 self._power_ranking_score_widgets: list[bui.Widget] = [] 175 176 self._season_popup_menu: PopupMenu | None = None 177 self._requested_season: str | None = None 178 self._season: str | None = None 179 180 # Take note of our account state; we'll refresh later if this 181 # changes. 182 self._account_state = plus.get_v1_account_state() 183 184 self._refresh() 185 self._restore_state() 186 187 # If we've got cached power-ranking data already, display it. 188 assert bui.app.classic is not None 189 info = bui.app.classic.accounts.get_cached_league_rank_data() 190 if info is not None: 191 self._update_for_league_rank_data(info) 192 193 self._update_timer = bui.AppTimer( 194 1.0, bui.WeakCall(self._update), repeat=True 195 ) 196 self._update(show=info is None) 197 198 @override 199 def get_main_window_state(self) -> bui.MainWindowState: 200 # Support recreating our window for back/refresh purposes. 201 cls = type(self) 202 return bui.BasicMainWindowState( 203 create_call=lambda transition, origin_widget: cls( 204 transition=transition, origin_widget=origin_widget 205 ) 206 ) 207 208 @override 209 def on_main_window_close(self) -> None: 210 self._save_state() 211 212 def _on_achievements_press(self) -> None: 213 from bauiv1lib.achievements import AchievementsWindow 214 215 # Only allow this for all-time or the current season (we 216 # currently don't keep specific achievement data for old 217 # seasons). 218 if self._season == 'a' or self._is_current_season: 219 prab = self._power_ranking_achievements_button 220 assert prab is not None 221 self.main_window_replace(AchievementsWindow(origin_widget=prab)) 222 else: 223 bui.screenmessage( 224 bui.Lstr( 225 resource='achievementsUnavailableForOldSeasonsText', 226 fallback_resource='unavailableText', 227 ), 228 color=(1, 0, 0), 229 ) 230 bui.getsound('error').play() 231 232 def _on_activity_mult_press(self) -> None: 233 from bauiv1lib import confirm 234 235 plus = bui.app.plus 236 assert plus is not None 237 238 txt = bui.Lstr( 239 resource=( 240 'coopSelectWindow.activenessAllTimeInfoText' 241 if self._season == 'a' 242 else 'coopSelectWindow.activenessInfoText' 243 ), 244 subs=[ 245 ( 246 '${MAX}', 247 str( 248 plus.get_v1_account_misc_read_val('activenessMax', 1.0) 249 ), 250 ) 251 ], 252 ) 253 confirm.ConfirmWindow( 254 txt, 255 cancel_button=False, 256 width=460, 257 height=150, 258 origin_widget=self._activity_mult_button, 259 ) 260 261 def _on_up_to_date_bonus_press(self) -> None: 262 from bauiv1lib import confirm 263 264 plus = bui.app.plus 265 assert plus is not None 266 267 txt = bui.Lstr( 268 resource='league.upToDateBonusDescriptionText', 269 subs=[ 270 ( 271 '${PERCENT}', 272 str( 273 plus.get_v1_account_misc_read_val( 274 'proPowerRankingBoost', 10 275 ) 276 ), 277 ), 278 ], 279 ) 280 confirm.ConfirmWindow( 281 txt, 282 cancel_button=False, 283 width=460, 284 height=130, 285 origin_widget=self._up_to_date_bonus_button, 286 ) 287 288 def _on_trophies_press(self) -> None: 289 from bauiv1lib.trophies import TrophiesWindow 290 291 info = self._league_rank_data 292 if info is not None: 293 prtb = self._power_ranking_trophies_button 294 assert prtb is not None 295 TrophiesWindow( 296 position=prtb.get_screen_space_center(), 297 data=info, 298 ) 299 else: 300 bui.getsound('error').play() 301 302 def _on_power_ranking_query_response( 303 self, data: dict[str, Any] | None 304 ) -> None: 305 self._doing_power_ranking_query = False 306 307 # Important: *only* cache this if we requested the current 308 # season. 309 if data is not None and data.get('s', None) is None: 310 assert bui.app.classic is not None 311 bui.app.classic.accounts.cache_league_rank_data(data) 312 313 # Always store a copy locally though (even for other seasons). 314 self._league_rank_data = copy.deepcopy(data) 315 self._update_for_league_rank_data(data) 316 317 def _restore_state(self) -> None: 318 pass 319 320 def _update(self, show: bool = False) -> None: 321 plus = bui.app.plus 322 assert plus is not None 323 324 cur_time = bui.apptime() 325 326 # If our account state has changed, refresh our UI. 327 account_state = plus.get_v1_account_state() 328 if account_state != self._account_state: 329 self._account_state = account_state 330 self._save_state() 331 self._refresh() 332 333 # And power ranking too. 334 if not self._doing_power_ranking_query: 335 self._last_power_ranking_query_time = None 336 337 # Send off a new power-ranking query if its been long enough or 338 # our requested season has changed or whatnot. 339 if not self._doing_power_ranking_query and ( 340 self._last_power_ranking_query_time is None 341 or cur_time - self._last_power_ranking_query_time > 30.0 342 ): 343 try: 344 if show: 345 bui.textwidget(edit=self._league_title_text, text='') 346 bui.textwidget(edit=self._league_text, text='') 347 bui.textwidget(edit=self._league_number_text, text='') 348 bui.textwidget(edit=self._your_power_ranking_text, text='') 349 bui.spinnerwidget(edit=self._loading_spinner, visible=True) 350 bui.textwidget(edit=self._to_ranked_text, text='') 351 bui.textwidget(edit=self._power_ranking_rank_text, text='') 352 bui.textwidget(edit=self._season_ends_text, text='') 353 bui.textwidget(edit=self._trophy_counts_reset_text, text='') 354 except Exception: 355 logging.exception('Error showing updated rank info.') 356 357 self._last_power_ranking_query_time = cur_time 358 self._doing_power_ranking_query = True 359 plus.power_ranking_query( 360 season=self._requested_season, 361 callback=bui.WeakCall(self._on_power_ranking_query_response), 362 ) 363 364 def _refresh(self) -> None: 365 # pylint: disable=too-many-statements 366 367 plus = bui.app.plus 368 assert plus is not None 369 370 # (Re)create the sub-container if need be. 371 if self._subcontainer is not None: 372 self._subcontainer.delete() 373 self._subcontainer = bui.containerwidget( 374 parent=self._scrollwidget, 375 size=(self._subcontainerwidth, self._subcontainerheight), 376 background=False, 377 ) 378 379 w_parent = self._subcontainer 380 v = self._subcontainerheight - 20 381 382 v -= 0 383 384 h2 = 80 385 v2 = v - 60 386 worth_color = (0.6, 0.6, 0.65) 387 tally_color = (0.5, 0.6, 0.8) 388 spc = 43 389 390 h_offs_tally = 150 391 tally_maxwidth = 120 392 v2 -= 70 393 394 bui.textwidget( 395 parent=w_parent, 396 position=(self._xoffs + h2 - 60, v2 + 106), 397 size=(0, 0), 398 flatness=1.0, 399 shadow=0.0, 400 text=bui.Lstr(resource='coopSelectWindow.pointsText'), 401 h_align='left', 402 v_align='center', 403 scale=0.8, 404 color=(1, 1, 1, 0.3), 405 maxwidth=200, 406 ) 407 408 self._power_ranking_achievements_button = bui.buttonwidget( 409 parent=w_parent, 410 position=(self._xoffs + h2 - 60, v2 + 10), 411 size=(200, 80), 412 icon=bui.gettexture('achievementsIcon'), 413 autoselect=True, 414 on_activate_call=bui.WeakCall(self._on_achievements_press), 415 up_widget=self._back_button, 416 left_widget=self._back_button, 417 color=(0.5, 0.5, 0.6), 418 textcolor=(0.7, 0.7, 0.8), 419 label='', 420 ) 421 422 self._power_ranking_achievement_total_text = bui.textwidget( 423 parent=w_parent, 424 position=(self._xoffs + h2 + h_offs_tally, v2 + 45), 425 size=(0, 0), 426 flatness=1.0, 427 shadow=0.0, 428 text='-', 429 h_align='left', 430 v_align='center', 431 scale=0.8, 432 color=tally_color, 433 maxwidth=tally_maxwidth, 434 ) 435 436 v2 -= 80 437 438 self._power_ranking_trophies_button = bui.buttonwidget( 439 parent=w_parent, 440 position=(self._xoffs + h2 - 60, v2 + 10), 441 size=(200, 80), 442 icon=bui.gettexture('medalSilver'), 443 autoselect=True, 444 on_activate_call=bui.WeakCall(self._on_trophies_press), 445 left_widget=self._back_button, 446 color=(0.5, 0.5, 0.6), 447 textcolor=(0.7, 0.7, 0.8), 448 label='', 449 ) 450 self._power_ranking_trophies_total_text = bui.textwidget( 451 parent=w_parent, 452 position=(self._xoffs + h2 + h_offs_tally, v2 + 45), 453 size=(0, 0), 454 flatness=1.0, 455 shadow=0.0, 456 text='-', 457 h_align='left', 458 v_align='center', 459 scale=0.8, 460 color=tally_color, 461 maxwidth=tally_maxwidth, 462 ) 463 464 v2 -= 100 465 466 bui.textwidget( 467 parent=w_parent, 468 position=(self._xoffs + h2 - 60, v2 + 86), 469 size=(0, 0), 470 flatness=1.0, 471 shadow=0.0, 472 text=bui.Lstr(resource='coopSelectWindow.multipliersText'), 473 h_align='left', 474 v_align='center', 475 scale=0.8, 476 color=(1, 1, 1, 0.3), 477 maxwidth=200, 478 ) 479 480 self._activity_mult_button: bui.Widget | None 481 if plus.get_v1_account_misc_read_val('act', False): 482 self._activity_mult_button = bui.buttonwidget( 483 parent=w_parent, 484 position=(self._xoffs + h2 - 60, v2 + 10), 485 size=(200, 60), 486 icon=bui.gettexture('heart'), 487 icon_color=(0.5, 0, 0.5), 488 label=bui.Lstr(resource='coopSelectWindow.activityText'), 489 autoselect=True, 490 on_activate_call=bui.WeakCall(self._on_activity_mult_press), 491 left_widget=self._back_button, 492 color=(0.5, 0.5, 0.6), 493 textcolor=(0.7, 0.7, 0.8), 494 ) 495 496 self._activity_mult_text = bui.textwidget( 497 parent=w_parent, 498 position=(self._xoffs + h2 + h_offs_tally, v2 + 40), 499 size=(0, 0), 500 flatness=1.0, 501 shadow=0.0, 502 text='-', 503 h_align='left', 504 v_align='center', 505 scale=0.8, 506 color=tally_color, 507 maxwidth=tally_maxwidth, 508 ) 509 v2 -= 65 510 else: 511 self._activity_mult_button = None 512 513 self._up_to_date_bonus_button = bui.buttonwidget( 514 parent=w_parent, 515 position=(self._xoffs + h2 - 60, v2 + 10), 516 size=(200, 60), 517 icon=bui.gettexture('logo'), 518 icon_color=(0.3, 0, 0.3), 519 label=bui.Lstr(resource='league.upToDateBonusText'), 520 autoselect=True, 521 on_activate_call=bui.WeakCall(self._on_up_to_date_bonus_press), 522 left_widget=self._back_button, 523 color=(0.5, 0.5, 0.6), 524 textcolor=(0.7, 0.7, 0.8), 525 ) 526 527 self._up_to_date_bonus_text = bui.textwidget( 528 parent=w_parent, 529 position=(self._xoffs + h2 + h_offs_tally, v2 + 40), 530 size=(0, 0), 531 flatness=1.0, 532 shadow=0.0, 533 text='-', 534 h_align='left', 535 v_align='center', 536 scale=0.8, 537 color=tally_color, 538 maxwidth=tally_maxwidth, 539 ) 540 v2 -= 30 541 542 v2 -= spc 543 bui.textwidget( 544 parent=w_parent, 545 position=(self._xoffs + h2 + h_offs_tally - 10 - 40, v2 + 35), 546 size=(0, 0), 547 flatness=1.0, 548 shadow=0.0, 549 text=bui.Lstr(resource='finalScoreText'), 550 h_align='right', 551 v_align='center', 552 scale=0.9, 553 color=worth_color, 554 maxwidth=150, 555 ) 556 self._power_ranking_total_text = bui.textwidget( 557 parent=w_parent, 558 position=(self._xoffs + h2 + h_offs_tally - 40, v2 + 35), 559 size=(0, 0), 560 flatness=1.0, 561 shadow=0.0, 562 text='-', 563 h_align='left', 564 v_align='center', 565 scale=0.9, 566 color=tally_color, 567 maxwidth=tally_maxwidth, 568 ) 569 570 self._season_show_text = bui.textwidget( 571 parent=w_parent, 572 position=(self._xoffs + 390 - 15, v - 20), 573 size=(0, 0), 574 color=(0.6, 0.6, 0.7), 575 maxwidth=200, 576 text=bui.Lstr(resource='showText'), 577 h_align='right', 578 v_align='center', 579 scale=0.8, 580 shadow=0, 581 flatness=1.0, 582 ) 583 584 self._league_title_text = bui.textwidget( 585 parent=w_parent, 586 position=(self._xoffs + 470, v - 97), 587 size=(0, 0), 588 color=(0.6, 0.6, 0.7), 589 maxwidth=230, 590 text='', 591 h_align='center', 592 v_align='center', 593 scale=0.9, 594 shadow=0, 595 flatness=1.0, 596 ) 597 598 self._league_text_scale = 1.8 599 self._league_text_maxwidth = 210 600 self._league_text = bui.textwidget( 601 parent=w_parent, 602 position=(self._xoffs + 470, v - 140), 603 size=(0, 0), 604 color=(1, 1, 1), 605 maxwidth=self._league_text_maxwidth, 606 text='-', 607 h_align='center', 608 v_align='center', 609 scale=self._league_text_scale, 610 shadow=1.0, 611 flatness=1.0, 612 ) 613 self._league_number_base_pos = (470, v - 140) 614 self._league_number_text = bui.textwidget( 615 parent=w_parent, 616 position=(self._xoffs + 470, v - 140), 617 size=(0, 0), 618 color=(1, 1, 1), 619 maxwidth=100, 620 text='', 621 h_align='left', 622 v_align='center', 623 scale=0.8, 624 shadow=1.0, 625 flatness=1.0, 626 ) 627 628 self._loading_spinner = bui.spinnerwidget( 629 parent=w_parent, 630 position=( 631 self._subcontainerwidth * 0.5, 632 self._subcontainerheight * 0.5, 633 ), 634 style='bomb', 635 size=64, 636 ) 637 self._your_power_ranking_text = bui.textwidget( 638 parent=w_parent, 639 position=(self._xoffs + 470, v - 142 - 70), 640 size=(0, 0), 641 color=(0.6, 0.6, 0.7), 642 maxwidth=230, 643 text='', 644 h_align='center', 645 v_align='center', 646 scale=0.9, 647 shadow=0, 648 flatness=1.0, 649 ) 650 651 self._to_ranked_text = bui.textwidget( 652 parent=w_parent, 653 position=(self._xoffs + 470, v - 250 - 70), 654 size=(0, 0), 655 color=(0.6, 0.6, 0.7), 656 maxwidth=230, 657 text='', 658 h_align='center', 659 v_align='center', 660 scale=0.8, 661 shadow=0, 662 flatness=1.0, 663 ) 664 665 self._power_ranking_rank_text = bui.textwidget( 666 parent=w_parent, 667 position=(self._xoffs + 473, v - 210 - 70), 668 size=(0, 0), 669 big=False, 670 text='-', 671 h_align='center', 672 v_align='center', 673 scale=1.0, 674 ) 675 676 self._season_ends_text = bui.textwidget( 677 parent=w_parent, 678 position=(self._xoffs + 470, v - 380), 679 size=(0, 0), 680 color=(0.6, 0.6, 0.6), 681 maxwidth=230, 682 text='', 683 h_align='center', 684 v_align='center', 685 scale=0.9, 686 shadow=0, 687 flatness=1.0, 688 ) 689 self._trophy_counts_reset_text = bui.textwidget( 690 parent=w_parent, 691 position=(self._xoffs + 470, v - 410), 692 size=(0, 0), 693 color=(0.5, 0.5, 0.5), 694 maxwidth=230, 695 text='Trophy counts will reset next season.', 696 h_align='center', 697 v_align='center', 698 scale=0.8, 699 shadow=0, 700 flatness=1.0, 701 ) 702 703 self._power_ranking_score_widgets = [] 704 705 self._power_ranking_score_v = v - 56 706 707 h = 707 708 v -= 451 709 710 self._see_more_button = bui.buttonwidget( 711 parent=w_parent, 712 label=self._rdict.seeMoreText, 713 position=(self._xoffs + h, v), 714 color=(0.5, 0.5, 0.6), 715 textcolor=(0.7, 0.7, 0.8), 716 size=(230, 60), 717 autoselect=True, 718 on_activate_call=bui.WeakCall(self._on_more_press), 719 ) 720 721 def _on_more_press(self) -> None: 722 plus = bui.app.plus 723 assert plus is not None 724 725 our_login_id = plus.get_v1_account_public_login_id() 726 if not self._can_do_more_button or our_login_id is None: 727 bui.getsound('error').play() 728 bui.screenmessage( 729 bui.Lstr(resource='unavailableText'), color=(1, 0, 0) 730 ) 731 return 732 if self._season is None: 733 season_str = '' 734 else: 735 season_str = '&season=' + ( 736 'all_time' if self._season == 'a' else self._season 737 ) 738 if self._league_url_arg != '': 739 league_str = '&league=' + self._league_url_arg 740 else: 741 league_str = '' 742 bui.open_url( 743 plus.get_master_server_address() 744 + '/highscores?list=powerRankings&v=2' 745 + league_str 746 + season_str 747 + '&player=' 748 + our_login_id 749 ) 750 751 def _update_for_league_rank_data(self, data: dict[str, Any] | None) -> None: 752 # pylint: disable=too-many-statements 753 # pylint: disable=too-many-branches 754 # pylint: disable=too-many-locals 755 if not self._root_widget: 756 return 757 plus = bui.app.plus 758 uiscale = bui.app.ui_v1.uiscale 759 assert plus is not None 760 assert bui.app.classic is not None 761 accounts = bui.app.classic.accounts 762 in_top = data is not None and data['rank'] is not None 763 eq_text = self._rdict.powerRankingPointsEqualsText 764 pts_txt = self._rdict.powerRankingPointsText 765 num_text = bui.Lstr(resource='numberText').evaluate() 766 do_percent = False 767 finished_season_unranked = False 768 self._can_do_more_button = True 769 extra_text = '' 770 if plus.get_v1_account_state() != 'signed_in': 771 status_text = ( 772 '(' + bui.Lstr(resource='notSignedInText').evaluate() + ')' 773 ) 774 elif in_top: 775 assert data is not None 776 status_text = num_text.replace('${NUMBER}', str(data['rank'])) 777 elif data is not None: 778 try: 779 # Handle old seasons where we didn't wind up ranked at 780 # the end. 781 if not data['scores']: 782 status_text = ( 783 self._rdict.powerRankingFinishedSeasonUnrankedText 784 ) 785 extra_text = '' 786 finished_season_unranked = True 787 self._can_do_more_button = False 788 else: 789 our_points = accounts.get_league_rank_points(data) 790 progress = float(our_points) / max(1, data['scores'][-1][1]) 791 status_text = str(int(progress * 100.0)) + '%' 792 extra_text = ( 793 '\n' 794 + self._rdict.powerRankingPointsToRankedText.replace( 795 '${CURRENT}', str(our_points) 796 ).replace('${REMAINING}', str(data['scores'][-1][1])) 797 ) 798 do_percent = True 799 except Exception: 800 logging.exception('Error updating power ranking.') 801 status_text = self._rdict.powerRankingNotInTopText.replace( 802 '${NUMBER}', str(data['listSize']) 803 ) 804 extra_text = '' 805 else: 806 status_text = '-' 807 808 self._season = data['s'] if data is not None else None 809 810 v = self._subcontainerheight - 20 811 popup_was_selected = False 812 if self._season_popup_menu is not None: 813 btn = self._season_popup_menu.get_button() 814 assert self._subcontainer 815 if self._subcontainer.get_selected_child() == btn: 816 popup_was_selected = True 817 btn.delete() 818 season_choices = [] 819 season_choices_display = [] 820 did_first = False 821 self._is_current_season = False 822 if data is not None: 823 # Build our list of seasons we have available. 824 for ssn in data['sl']: 825 season_choices.append(ssn) 826 if ssn != 'a' and not did_first: 827 season_choices_display.append( 828 bui.Lstr( 829 resource='league.currentSeasonText', 830 subs=[('${NUMBER}', ssn)], 831 ) 832 ) 833 did_first = True 834 835 # If we either did not specify a season or specified 836 # the first, we're looking at the current. 837 if self._season in [ssn, None]: 838 self._is_current_season = True 839 elif ssn == 'a': 840 season_choices_display.append( 841 bui.Lstr(resource='league.allTimeText') 842 ) 843 else: 844 season_choices_display.append( 845 bui.Lstr( 846 resource='league.seasonText', 847 subs=[('${NUMBER}', ssn)], 848 ) 849 ) 850 assert self._subcontainer 851 self._season_popup_menu = PopupMenu( 852 parent=self._subcontainer, 853 position=(self._xoffs + 390, v - 45), 854 width=150, 855 button_size=(200, 50), 856 choices=season_choices, 857 on_value_change_call=bui.WeakCall(self._on_season_change), 858 choices_display=season_choices_display, 859 current_choice=self._season, 860 ) 861 if popup_was_selected: 862 bui.containerwidget( 863 edit=self._subcontainer, 864 selected_child=self._season_popup_menu.get_button(), 865 ) 866 bui.widget(edit=self._see_more_button, show_buffer_bottom=100) 867 bui.widget( 868 edit=self._season_popup_menu.get_button(), 869 up_widget=self._back_button, 870 ) 871 if uiscale is not bui.UIScale.SMALL: 872 bui.widget( 873 edit=self._back_button, 874 down_widget=self._power_ranking_achievements_button, 875 right_widget=self._season_popup_menu.get_button(), 876 ) 877 878 bui.textwidget( 879 edit=self._league_title_text, 880 text=( 881 '' 882 if self._season == 'a' 883 else bui.Lstr(resource='league.leagueText') 884 ), 885 ) 886 887 if data is None: 888 lname = '' 889 lnum = '' 890 lcolor = (1, 1, 1) 891 self._league_url_arg = '' 892 elif self._season == 'a': 893 lname = bui.Lstr(resource='league.allTimeText').evaluate() 894 lnum = '' 895 lcolor = (1, 1, 1) 896 self._league_url_arg = '' 897 else: 898 lnum = ('[' + str(data['l']['i']) + ']') if data['l']['i2'] else '' 899 lname = bui.Lstr( 900 translate=('leagueNames', data['l']['n']) 901 ).evaluate() 902 lcolor = data['l']['c'] 903 self._league_url_arg = ( 904 data['l']['n'] + '_' + str(data['l']['i']) 905 ).lower() 906 907 to_end_string: bui.Lstr | str 908 if data is None or self._season == 'a' or data['se'] is None: 909 to_end_string = '' 910 show_season_end = False 911 else: 912 show_season_end = True 913 days_to_end = data['se'][0] 914 minutes_to_end = data['se'][1] 915 if days_to_end > 0: 916 to_end_string = bui.Lstr( 917 resource='league.seasonEndsDaysText', 918 subs=[('${NUMBER}', str(days_to_end))], 919 ) 920 elif days_to_end == 0 and minutes_to_end >= 60: 921 to_end_string = bui.Lstr( 922 resource='league.seasonEndsHoursText', 923 subs=[('${NUMBER}', str(minutes_to_end // 60))], 924 ) 925 elif days_to_end == 0 and minutes_to_end >= 0: 926 to_end_string = bui.Lstr( 927 resource='league.seasonEndsMinutesText', 928 subs=[('${NUMBER}', str(minutes_to_end))], 929 ) 930 else: 931 to_end_string = bui.Lstr( 932 resource='league.seasonEndedDaysAgoText', 933 subs=[('${NUMBER}', str(-(days_to_end + 1)))], 934 ) 935 936 bui.textwidget(edit=self._season_ends_text, text=to_end_string) 937 bui.textwidget( 938 edit=self._trophy_counts_reset_text, 939 text=( 940 bui.Lstr(resource='league.trophyCountsResetText') 941 if self._is_current_season and show_season_end 942 else '' 943 ), 944 ) 945 946 bui.textwidget(edit=self._league_text, text=lname, color=lcolor) 947 l_text_width = min( 948 self._league_text_maxwidth, 949 bui.get_string_width(lname, suppress_warning=True) 950 * self._league_text_scale, 951 ) 952 bui.textwidget( 953 edit=self._league_number_text, 954 text=lnum, 955 color=lcolor, 956 position=( 957 self._xoffs 958 + self._league_number_base_pos[0] 959 + l_text_width * 0.5 960 + 8, 961 self._league_number_base_pos[1] + 10, 962 ), 963 ) 964 bui.textwidget( 965 edit=self._to_ranked_text, 966 text=( 967 bui.Lstr(resource='coopSelectWindow.toRankedText').evaluate() 968 + '' 969 + extra_text 970 if do_percent 971 else '' 972 ), 973 ) 974 975 bui.textwidget( 976 edit=self._your_power_ranking_text, 977 text=( 978 bui.Lstr( 979 resource='rankText', 980 fallback_resource='coopSelectWindow.yourPowerRankingText', 981 ) 982 if (not do_percent) 983 else '' 984 ), 985 ) 986 bui.spinnerwidget(edit=self._loading_spinner, visible=False) 987 988 bui.textwidget( 989 edit=self._power_ranking_rank_text, 990 position=(self._xoffs + 473, v - 70 - (170 if do_percent else 220)), 991 text=status_text, 992 big=(in_top or do_percent), 993 scale=( 994 3.0 995 if (in_top or do_percent) 996 else 0.7 if finished_season_unranked else 1.0 997 ), 998 ) 999 1000 if self._activity_mult_button is not None: 1001 if data is None or data['act'] is None: 1002 bui.buttonwidget( 1003 edit=self._activity_mult_button, 1004 textcolor=(0.7, 0.7, 0.8, 0.5), 1005 icon_color=(0.5, 0, 0.5, 0.3), 1006 ) 1007 bui.textwidget(edit=self._activity_mult_text, text=' -') 1008 else: 1009 bui.buttonwidget( 1010 edit=self._activity_mult_button, 1011 textcolor=(0.7, 0.7, 0.8, 1.0), 1012 icon_color=(0.5, 0, 0.5, 1.0), 1013 ) 1014 bui.textwidget( 1015 edit=self._activity_mult_text, 1016 text=f'x {data['act']:.2f}', 1017 ) 1018 1019 # This used to be a bonus for 'BombSquad Pro' holders, but since 1020 # we're transitioning away from that it is now a bonus for 1021 # everyone running a recent-ish version of the game. 1022 1023 have_up_to_date_bonus = data is not None 1024 pro_mult = ( 1025 1.0 1026 + float( 1027 plus.get_v1_account_misc_read_val('proPowerRankingBoost', 0.0) 1028 ) 1029 * 0.01 1030 ) 1031 bui.textwidget( 1032 edit=self._up_to_date_bonus_text, 1033 text=( 1034 ' -' 1035 if (data is None or not have_up_to_date_bonus) 1036 else f'x {pro_mult:.2f}' 1037 ), 1038 ) 1039 bui.buttonwidget( 1040 edit=self._up_to_date_bonus_button, 1041 textcolor=(0.7, 0.7, 0.8, (1.0 if have_up_to_date_bonus else 0.5)), 1042 icon_color=( 1043 (0.5, 0, 0.5) if have_up_to_date_bonus else (0.5, 0, 0.5, 0.2) 1044 ), 1045 ) 1046 bui.buttonwidget( 1047 edit=self._power_ranking_achievements_button, 1048 label=('' if data is None else str(data['a']) + ' ') 1049 + bui.Lstr(resource='achievementsText').evaluate(), 1050 ) 1051 1052 # For the achievement value, use the number they gave us for 1053 # non-current seasons; otherwise calc our own. 1054 total_ach_value = 0 1055 for ach in bui.app.classic.ach.achievements: 1056 if ach.complete: 1057 total_ach_value += ach.power_ranking_value 1058 if self._season != 'a' and not self._is_current_season: 1059 if data is not None and 'at' in data: 1060 total_ach_value = data['at'] 1061 1062 bui.textwidget( 1063 edit=self._power_ranking_achievement_total_text, 1064 text=( 1065 '-' 1066 if data is None 1067 else ('+ ' + pts_txt.replace('${NUMBER}', str(total_ach_value))) 1068 ), 1069 ) 1070 1071 total_trophies_count = accounts.get_league_rank_points( 1072 data, 'trophyCount' 1073 ) 1074 total_trophies_value = accounts.get_league_rank_points(data, 'trophies') 1075 bui.buttonwidget( 1076 edit=self._power_ranking_trophies_button, 1077 label=('' if data is None else str(total_trophies_count) + ' ') 1078 + bui.Lstr(resource='trophiesText').evaluate(), 1079 ) 1080 bui.textwidget( 1081 edit=self._power_ranking_trophies_total_text, 1082 text=( 1083 '-' 1084 if data is None 1085 else ( 1086 '+ ' 1087 + pts_txt.replace('${NUMBER}', str(total_trophies_value)) 1088 ) 1089 ), 1090 ) 1091 1092 bui.textwidget( 1093 edit=self._power_ranking_total_text, 1094 text=( 1095 '-' 1096 if data is None 1097 else eq_text.replace( 1098 '${NUMBER}', str(accounts.get_league_rank_points(data)) 1099 ) 1100 ), 1101 ) 1102 for widget in self._power_ranking_score_widgets: 1103 widget.delete() 1104 self._power_ranking_score_widgets = [] 1105 1106 scores = data['scores'] if data is not None else [] 1107 tally_color = (0.5, 0.6, 0.8) 1108 w_parent = self._subcontainer 1109 v2 = self._power_ranking_score_v 1110 1111 for score in scores: 1112 h2 = 680 1113 is_us = score[3] 1114 self._power_ranking_score_widgets.append( 1115 bui.textwidget( 1116 parent=w_parent, 1117 position=(self._xoffs + h2 - 20, v2), 1118 size=(0, 0), 1119 color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7), 1120 maxwidth=40, 1121 flatness=1.0, 1122 shadow=0.0, 1123 text=num_text.replace('${NUMBER}', str(score[0])), 1124 h_align='right', 1125 v_align='center', 1126 scale=0.5, 1127 ) 1128 ) 1129 self._power_ranking_score_widgets.append( 1130 bui.textwidget( 1131 parent=w_parent, 1132 position=(self._xoffs + h2 + 20, v2), 1133 size=(0, 0), 1134 color=(1, 1, 1) if is_us else tally_color, 1135 maxwidth=60, 1136 text=str(score[1]), 1137 flatness=1.0, 1138 shadow=0.0, 1139 h_align='center', 1140 v_align='center', 1141 scale=0.7, 1142 ) 1143 ) 1144 txt = bui.textwidget( 1145 parent=w_parent, 1146 position=(self._xoffs + h2 + 60, v2 - (28 * 0.5) / 0.9), 1147 size=(210 / 0.9, 28), 1148 color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6), 1149 maxwidth=210, 1150 flatness=1.0, 1151 shadow=0.0, 1152 autoselect=True, 1153 selectable=True, 1154 click_activate=True, 1155 text=score[2], 1156 h_align='left', 1157 v_align='center', 1158 scale=0.9, 1159 ) 1160 self._power_ranking_score_widgets.append(txt) 1161 bui.textwidget( 1162 edit=txt, 1163 on_activate_call=bui.Call( 1164 self._show_account_info, score[4], txt 1165 ), 1166 ) 1167 assert self._season_popup_menu is not None 1168 bui.widget( 1169 edit=txt, left_widget=self._season_popup_menu.get_button() 1170 ) 1171 v2 -= 28 1172 1173 def _show_account_info( 1174 self, account_id: str, textwidget: bui.Widget 1175 ) -> None: 1176 from bauiv1lib.account.viewer import AccountViewerWindow 1177 1178 bui.getsound('swish').play() 1179 AccountViewerWindow( 1180 account_id=account_id, position=textwidget.get_screen_space_center() 1181 ) 1182 1183 def _on_season_change(self, value: str) -> None: 1184 self._requested_season = value 1185 self._last_power_ranking_query_time = None # Update asap. 1186 self._update(show=True) 1187 1188 def _save_state(self) -> None: 1189 pass
Window for showing league rank.
LeagueRankWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
23 def __init__( 24 self, 25 transition: str | None = 'in_right', 26 origin_widget: bui.Widget | None = None, 27 ): 28 # pylint: disable=too-many-statements 29 plus = bui.app.plus 30 assert plus is not None 31 32 bui.set_analytics_screen('League Rank Window') 33 34 self._league_rank_data: dict[str, Any] | None = None 35 36 self._power_ranking_achievements_button: bui.Widget | None = None 37 self._up_to_date_bonus_button: bui.Widget | None = None 38 self._power_ranking_trophies_button: bui.Widget | None = None 39 self._league_title_text: bui.Widget | None = None 40 self._league_text: bui.Widget | None = None 41 self._league_number_text: bui.Widget | None = None 42 self._your_power_ranking_text: bui.Widget | None = None 43 self._loading_spinner: bui.Widget | None = None 44 self._season_ends_text: bui.Widget | None = None 45 self._power_ranking_rank_text: bui.Widget | None = None 46 self._to_ranked_text: bui.Widget | None = None 47 self._trophy_counts_reset_text: bui.Widget | None = None 48 49 assert bui.app.classic is not None 50 uiscale = bui.app.ui_v1.uiscale 51 self._width = 1500 if uiscale is bui.UIScale.SMALL else 1120 52 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 53 self._height = ( 54 1000 55 if uiscale is bui.UIScale.SMALL 56 else 710 if uiscale is bui.UIScale.MEDIUM else 800 57 ) 58 self._r = 'coopSelectWindow' 59 self._rdict = bui.app.lang.get_resource(self._r) 60 # top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 61 62 # self._xoffs = 80.0 if uiscale is bui.UIScale.SMALL else 0 63 self._xoffs = 40 64 65 self._league_url_arg = '' 66 67 self._is_current_season = False 68 self._can_do_more_button = True 69 70 # Do some fancy math to fill all available screen area up to the 71 # size of our backing container. This lets us fit to the exact 72 # screen shape at small ui scale. 73 screensize = bui.get_virtual_screen_size() 74 scale = ( 75 1.3 76 if uiscale is bui.UIScale.SMALL 77 else 0.93 if uiscale is bui.UIScale.MEDIUM else 0.8 78 ) 79 # Calc screen size in our local container space and clamp to a 80 # bit smaller than our container size. 81 target_width = min(self._width - 130, screensize[0] / scale) 82 target_height = min(self._height - 130, screensize[1] / scale) 83 84 # To get top/left coords, go to the center of our window and 85 # offset by half the width/height of our target area. 86 yoffs = 0.5 * self._height + 0.5 * target_height + 30.0 87 88 self._scroll_width = target_width 89 self._scroll_height = target_height - 35 90 scroll_bottom = yoffs - 80 - self._scroll_height 91 92 super().__init__( 93 root_widget=bui.containerwidget( 94 size=(self._width, self._height), 95 stack_offset=( 96 (0, 0) 97 if uiscale is bui.UIScale.SMALL 98 else (0, 10) if uiscale is bui.UIScale.MEDIUM else (0, 0) 99 ), 100 scale=scale, 101 toolbar_visibility=( 102 'menu_minimal' 103 if uiscale is bui.UIScale.SMALL 104 else 'menu_full' 105 ), 106 ), 107 transition=transition, 108 origin_widget=origin_widget, 109 # We're affected by screen size only at small ui-scale. 110 refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, 111 ) 112 113 if uiscale is bui.UIScale.SMALL: 114 self._back_button = bui.get_special_widget('back_button') 115 bui.containerwidget( 116 edit=self._root_widget, on_cancel_call=self.main_window_back 117 ) 118 else: 119 self._back_button = bui.buttonwidget( 120 parent=self._root_widget, 121 position=(75 + x_inset, yoffs - 60), 122 size=(60, 55), 123 scale=1.2, 124 autoselect=True, 125 label=bui.charstr(bui.SpecialChar.BACK), 126 button_type='backSmall', 127 on_activate_call=self.main_window_back, 128 ) 129 bui.containerwidget( 130 edit=self._root_widget, 131 cancel_button=self._back_button, 132 selected_child=self._back_button, 133 ) 134 135 self._title_text = bui.textwidget( 136 parent=self._root_widget, 137 position=( 138 self._width * 0.5, 139 yoffs - (55 if uiscale is bui.UIScale.SMALL else 30), 140 ), 141 size=(0, 0), 142 text=bui.Lstr( 143 resource='league.leagueRankText', 144 fallback_resource='coopSelectWindow.powerRankingText', 145 ), 146 h_align='center', 147 color=bui.app.ui_v1.title_color, 148 scale=1.2 if uiscale is bui.UIScale.SMALL else 1.3, 149 maxwidth=600, 150 v_align='center', 151 ) 152 153 self._scrollwidget = bui.scrollwidget( 154 parent=self._root_widget, 155 highlight=False, 156 size=(self._scroll_width, self._scroll_height), 157 position=( 158 self._width * 0.5 - self._scroll_width * 0.5, 159 scroll_bottom, 160 ), 161 center_small_content=True, 162 center_small_content_horizontally=True, 163 border_opacity=0.4, 164 ) 165 bui.widget(edit=self._scrollwidget, autoselect=True) 166 bui.containerwidget(edit=self._scrollwidget, claims_left_right=True) 167 168 self._last_power_ranking_query_time: float | None = None 169 self._doing_power_ranking_query = False 170 171 self._subcontainer: bui.Widget | None = None 172 self._subcontainerwidth = 1024 173 self._subcontainerheight = 483 174 self._power_ranking_score_widgets: list[bui.Widget] = [] 175 176 self._season_popup_menu: PopupMenu | None = None 177 self._requested_season: str | None = None 178 self._season: str | None = None 179 180 # Take note of our account state; we'll refresh later if this 181 # changes. 182 self._account_state = plus.get_v1_account_state() 183 184 self._refresh() 185 self._restore_state() 186 187 # If we've got cached power-ranking data already, display it. 188 assert bui.app.classic is not None 189 info = bui.app.classic.accounts.get_cached_league_rank_data() 190 if info is not None: 191 self._update_for_league_rank_data(info) 192 193 self._update_timer = bui.AppTimer( 194 1.0, bui.WeakCall(self._update), repeat=True 195 ) 196 self._update(show=info is None)
Create a MainWindow given a root widget and transition info.
Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.
198 @override 199 def get_main_window_state(self) -> bui.MainWindowState: 200 # Support recreating our window for back/refresh purposes. 201 cls = type(self) 202 return bui.BasicMainWindowState( 203 create_call=lambda transition, origin_widget: cls( 204 transition=transition, origin_widget=origin_widget 205 ) 206 )
Return a WindowState to recreate this window, if supported.