bauiv1lib.play
Provides the top level play window.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides the top level play window.""" 4 5from __future__ import annotations 6 7import logging 8from typing import override, TYPE_CHECKING 9 10import bascenev1 as bs 11import bauiv1 as bui 12 13if TYPE_CHECKING: 14 from bauiv1 import MainWindowState 15 16 17class PlaylistSelectContext: 18 """For using PlayWindow to select a playlist instead of running game.""" 19 20 back_state: MainWindowState | None = None 21 22 23class PlayWindow(bui.MainWindow): 24 """Window for selecting overall play type.""" 25 26 def __init__( 27 self, 28 transition: str | None = 'in_right', 29 origin_widget: bui.Widget | None = None, 30 playlist_select_context: PlaylistSelectContext | None = None, 31 ): 32 # pylint: disable=too-many-statements 33 # pylint: disable=too-many-locals 34 35 # Preload some modules we use in a background thread so we won't 36 # have a visual hitch when the user taps them. 37 bui.app.threadpool.submit_no_wait(self._preload_modules) 38 39 classic = bui.app.classic 40 assert classic is not None 41 42 self._playlist_select_context = playlist_select_context 43 44 uiscale = bui.app.ui_v1.uiscale 45 width = 1300 if uiscale is bui.UIScale.SMALL else 1000 46 height = 1000 if uiscale is bui.UIScale.SMALL else 550 47 48 button_width = 400.0 49 button_height = 360.0 50 button_spacing = 3.0 51 52 if origin_widget is not None: 53 54 # Need to store this ourself since we can function as a 55 # non-main window. 56 self._transition_out = 'out_scale' 57 else: 58 self._transition_out = 'out_right' 59 60 self._r = 'playWindow' 61 62 # Do some fancy math to fill all available screen area up to the 63 # size of our backing container. This lets us fit to the exact 64 # screen shape at small ui scale. 65 screensize = bui.get_virtual_screen_size() 66 safesize = bui.get_virtual_safe_area_size() 67 68 # We're a generally widescreen shaped window, so bump our 69 # overall scale up a bit when screen width is wider than safe 70 # bounds to take advantage of the extra space. 71 smallscale = min(1.6, 1.35 * screensize[0] / safesize[0]) 72 73 scale = ( 74 smallscale 75 if uiscale is bui.UIScale.SMALL 76 else 0.9 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_height = min(height - 80, screensize[1] / scale) 81 82 # To get top/left coords, go to the center of our window and 83 # offset by half the width/height of our target area. 84 yoffs = 0.5 * height + 0.5 * target_height + 30.0 85 86 super().__init__( 87 root_widget=bui.containerwidget( 88 size=(width, height), 89 toolbar_visibility=( 90 'menu_full' 91 if playlist_select_context is None 92 else 'menu_minimal' 93 ), 94 scale=scale, 95 ), 96 transition=transition, 97 origin_widget=origin_widget, 98 # We're affected by screen size only at small ui-scale. 99 refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, 100 ) 101 102 self._back_button: bui.Widget | None 103 if uiscale is bui.UIScale.SMALL: 104 self._back_button = None 105 bui.containerwidget( 106 edit=self._root_widget, 107 on_cancel_call=self.main_window_back, 108 ) 109 else: 110 self._back_button = bui.buttonwidget( 111 parent=self._root_widget, 112 position=(50, yoffs - 100), 113 size=(60, 60), 114 scale=1.1, 115 text_res_scale=1.5, 116 text_scale=1.2, 117 autoselect=True, 118 label=bui.charstr(bui.SpecialChar.BACK), 119 button_type='backSmall', 120 on_activate_call=self.main_window_back, 121 ) 122 bui.containerwidget( 123 edit=self._root_widget, cancel_button=self._back_button 124 ) 125 126 bui.textwidget( 127 parent=self._root_widget, 128 position=( 129 width * 0.5, 130 yoffs - (50 if uiscale is bui.UIScale.SMALL else 70), 131 ), 132 size=(0, 0), 133 text=bui.Lstr( 134 resource=( 135 (f'{self._r}.titleText') 136 if self._playlist_select_context is None 137 else 'playlistsText' 138 ) 139 ), 140 scale=1.2 if uiscale is bui.UIScale.SMALL else 1.7, 141 res_scale=2.0, 142 maxwidth=250, 143 color=bui.app.ui_v1.heading_color, 144 h_align='center', 145 v_align='center', 146 ) 147 148 scl = 0.75 if self._playlist_select_context is None else 0.68 149 v = height * 0.5 - button_height * scl * 0.5 - 20.0 150 clr = (0.6, 0.7, 0.6, 1.0) 151 152 bcount = 3 if self._playlist_select_context is None else 2 153 154 total_b_width = ( 155 bcount * button_width * scl + (bcount - 1) * button_spacing 156 ) 157 hoffs = (width - total_b_width) * 0.5 158 159 self._lineup_tex = bui.gettexture('playerLineup') 160 angry_computer_transparent_mesh = bui.getmesh( 161 'angryComputerTransparent' 162 ) 163 self._lineup_1_transparent_mesh = bui.getmesh( 164 'playerLineup1Transparent' 165 ) 166 self._lineup_2_transparent_mesh = bui.getmesh( 167 'playerLineup2Transparent' 168 ) 169 self._lineup_3_transparent_mesh = bui.getmesh( 170 'playerLineup3Transparent' 171 ) 172 self._lineup_4_transparent_mesh = bui.getmesh( 173 'playerLineup4Transparent' 174 ) 175 self._eyes_mesh = bui.getmesh('plasticEyesTransparent') 176 177 self._coop_button: bui.Widget | None = None 178 179 # Only show coop button in regular variant. 180 if self._playlist_select_context is None: 181 self._coop_button = btn = bui.buttonwidget( 182 parent=self._root_widget, 183 position=(hoffs, v), 184 size=( 185 scl * button_width, 186 scl * button_height, 187 ), 188 extra_touch_border_scale=0.1, 189 autoselect=True, 190 label='', 191 button_type='square', 192 on_activate_call=self._coop, 193 ) 194 195 if uiscale is bui.UIScale.SMALL: 196 bui.widget( 197 edit=btn, 198 left_widget=bui.get_special_widget('back_button'), 199 ) 200 bui.widget( 201 edit=btn, 202 up_widget=bui.get_special_widget('account_button'), 203 ) 204 bui.widget( 205 edit=btn, 206 down_widget=bui.get_special_widget('settings_button'), 207 ) 208 209 self._draw_dude( 210 0, 211 btn, 212 hoffs, 213 v, 214 scl, 215 position=(140, 30), 216 color=(0.72, 0.4, 1.0), 217 ) 218 self._draw_dude( 219 1, 220 btn, 221 hoffs, 222 v, 223 scl, 224 position=(185, 53), 225 color=(0.71, 0.5, 1.0), 226 ) 227 self._draw_dude( 228 2, 229 btn, 230 hoffs, 231 v, 232 scl, 233 position=(220, 27), 234 color=(0.67, 0.44, 1.0), 235 ) 236 self._draw_dude( 237 3, btn, hoffs, v, scl, position=(255, 57), color=(0.7, 0.3, 1.0) 238 ) 239 bui.imagewidget( 240 parent=self._root_widget, 241 draw_controller=btn, 242 position=(hoffs + scl * 230, v + scl * 153), 243 size=(scl * 115, scl * 115), 244 texture=self._lineup_tex, 245 mesh_transparent=angry_computer_transparent_mesh, 246 ) 247 248 bui.textwidget( 249 parent=self._root_widget, 250 draw_controller=btn, 251 position=(hoffs + scl * (-10), v + scl * 95), 252 size=(scl * button_width, scl * 50), 253 text=bui.Lstr( 254 resource='playModes.singlePlayerCoopText', 255 fallback_resource='playModes.coopText', 256 ), 257 maxwidth=scl * button_width * 0.7, 258 res_scale=1.5, 259 h_align='center', 260 v_align='center', 261 color=(0.7, 0.9, 0.7, 1.0), 262 scale=scl * 1.5, 263 ) 264 265 bui.textwidget( 266 parent=self._root_widget, 267 draw_controller=btn, 268 position=(hoffs + scl * (-10), v + (scl * 54)), 269 size=(scl * button_width, scl * 30), 270 text=bui.Lstr(resource=f'{self._r}.oneToFourPlayersText'), 271 h_align='center', 272 v_align='center', 273 scale=0.83 * scl, 274 flatness=1.0, 275 maxwidth=scl * button_width * 0.7, 276 color=clr, 277 ) 278 279 hoffs += scl * button_width + button_spacing 280 281 self._teams_button = btn = bui.buttonwidget( 282 parent=self._root_widget, 283 position=(hoffs, v), 284 size=( 285 scl * button_width, 286 scl * button_height, 287 ), 288 extra_touch_border_scale=0.1, 289 autoselect=True, 290 label='', 291 button_type='square', 292 on_activate_call=self._team_tourney, 293 ) 294 295 xxx = -14 296 self._draw_dude( 297 2, 298 btn, 299 hoffs, 300 v, 301 scl, 302 position=(xxx + 148, 30), 303 color=(0.2, 0.4, 1.0), 304 ) 305 self._draw_dude( 306 3, 307 btn, 308 hoffs, 309 v, 310 scl, 311 position=(xxx + 181, 53), 312 color=(0.3, 0.4, 1.0), 313 ) 314 self._draw_dude( 315 1, 316 btn, 317 hoffs, 318 v, 319 scl, 320 position=(xxx + 216, 33), 321 color=(0.3, 0.5, 1.0), 322 ) 323 self._draw_dude( 324 0, 325 btn, 326 hoffs, 327 v, 328 scl, 329 position=(xxx + 245, 57), 330 color=(0.3, 0.5, 1.0), 331 ) 332 333 xxx = 155 334 self._draw_dude( 335 0, 336 btn, 337 hoffs, 338 v, 339 scl, 340 position=(xxx + 151, 30), 341 color=(1.0, 0.5, 0.4), 342 ) 343 self._draw_dude( 344 1, 345 btn, 346 hoffs, 347 v, 348 scl, 349 position=(xxx + 189, 53), 350 color=(1.0, 0.58, 0.58), 351 ) 352 self._draw_dude( 353 3, 354 btn, 355 hoffs, 356 v, 357 scl, 358 position=(xxx + 223, 27), 359 color=(1.0, 0.5, 0.5), 360 ) 361 self._draw_dude( 362 2, 363 btn, 364 hoffs, 365 v, 366 scl, 367 position=(xxx + 257, 57), 368 color=(1.0, 0.5, 0.5), 369 ) 370 371 bui.textwidget( 372 parent=self._root_widget, 373 draw_controller=btn, 374 position=(hoffs + scl * (-10), v + scl * 95), 375 size=(scl * button_width, scl * 50), 376 text=bui.Lstr( 377 resource='playModes.teamsText', fallback_resource='teamsText' 378 ), 379 res_scale=1.5, 380 maxwidth=scl * button_width * 0.7, 381 h_align='center', 382 v_align='center', 383 color=(0.7, 0.9, 0.7, 1.0), 384 scale=scl * 1.5, 385 ) 386 bui.textwidget( 387 parent=self._root_widget, 388 draw_controller=btn, 389 position=(hoffs + scl * (-10), v + (scl * 54)), 390 size=(scl * button_width, scl * 30), 391 text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'), 392 h_align='center', 393 v_align='center', 394 res_scale=1.5, 395 scale=0.83 * scl, 396 flatness=1.0, 397 maxwidth=scl * button_width * 0.7, 398 color=clr, 399 ) 400 401 hoffs += scl * button_width + button_spacing 402 self._free_for_all_button = btn = bui.buttonwidget( 403 parent=self._root_widget, 404 position=(hoffs, v), 405 size=(scl * button_width, scl * button_height), 406 extra_touch_border_scale=0.1, 407 autoselect=True, 408 label='', 409 button_type='square', 410 on_activate_call=self._free_for_all, 411 ) 412 413 xxx = -5 414 self._draw_dude( 415 0, 416 btn, 417 hoffs, 418 v, 419 scl, 420 position=(xxx + 140, 30), 421 color=(0.4, 1.0, 0.4), 422 ) 423 self._draw_dude( 424 3, 425 btn, 426 hoffs, 427 v, 428 scl, 429 position=(xxx + 185, 53), 430 color=(1.0, 0.4, 0.5), 431 ) 432 self._draw_dude( 433 1, 434 btn, 435 hoffs, 436 v, 437 scl, 438 position=(xxx + 220, 27), 439 color=(0.4, 0.5, 1.0), 440 ) 441 self._draw_dude( 442 2, 443 btn, 444 hoffs, 445 v, 446 scl, 447 position=(xxx + 255, 57), 448 color=(0.5, 1.0, 0.4), 449 ) 450 xxx = 140 451 self._draw_dude( 452 2, 453 btn, 454 hoffs, 455 v, 456 scl, 457 position=(xxx + 148, 30), 458 color=(1.0, 0.9, 0.4), 459 ) 460 self._draw_dude( 461 0, 462 btn, 463 hoffs, 464 v, 465 scl, 466 position=(xxx + 182, 53), 467 color=(0.7, 1.0, 0.5), 468 ) 469 self._draw_dude( 470 3, 471 btn, 472 hoffs, 473 v, 474 scl, 475 position=(xxx + 233, 27), 476 color=(0.7, 0.5, 0.9), 477 ) 478 self._draw_dude( 479 1, 480 btn, 481 hoffs, 482 v, 483 scl, 484 position=(xxx + 266, 53), 485 color=(0.4, 0.5, 0.8), 486 ) 487 bui.textwidget( 488 parent=self._root_widget, 489 draw_controller=btn, 490 position=(hoffs + scl * (-10), v + scl * 95), 491 size=(scl * button_width, scl * 50), 492 text=bui.Lstr( 493 resource='playModes.freeForAllText', 494 fallback_resource='freeForAllText', 495 ), 496 maxwidth=scl * button_width * 0.7, 497 h_align='center', 498 v_align='center', 499 color=(0.7, 0.9, 0.7, 1.0), 500 scale=scl * 1.5, 501 ) 502 bui.textwidget( 503 parent=self._root_widget, 504 draw_controller=btn, 505 position=(hoffs + scl * (-10), v + (scl * 54)), 506 size=(scl * button_width, scl * 30), 507 text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'), 508 h_align='center', 509 v_align='center', 510 scale=0.83 * scl, 511 flatness=1.0, 512 maxwidth=scl * button_width * 0.7, 513 color=clr, 514 ) 515 516 if uiscale is bui.UIScale.SMALL: 517 bui.containerwidget( 518 edit=self._root_widget, 519 selected_child=( 520 self._coop_button 521 if self._playlist_select_context is None 522 else self._teams_button 523 ), 524 ) 525 else: 526 bui.containerwidget( 527 edit=self._root_widget, 528 selected_child=( 529 self._coop_button 530 if self._playlist_select_context is None 531 else self._teams_button 532 ), 533 ) 534 535 self._restore_state() 536 537 @override 538 def get_main_window_state(self) -> bui.MainWindowState: 539 # Support recreating our window for back/refresh purposes. 540 cls = type(self) 541 542 # Pull any values out of self here; if we do it in the lambda 543 # we'll keep our window alive inadvertantly. 544 playlist_select_context = self._playlist_select_context 545 return bui.BasicMainWindowState( 546 create_call=lambda transition, origin_widget: cls( 547 transition=transition, 548 origin_widget=origin_widget, 549 playlist_select_context=playlist_select_context, 550 ) 551 ) 552 553 @override 554 def on_main_window_close(self) -> None: 555 self._save_state() 556 557 @staticmethod 558 def _preload_modules() -> None: 559 """Preload modules we use; avoids hitches (called in bg thread).""" 560 import bauiv1lib.mainmenu as _unused1 561 import bauiv1lib.account as _unused2 562 import bauiv1lib.coop.browser as _unused3 563 import bauiv1lib.playlist.browser as _unused4 564 565 def _coop(self) -> None: 566 # pylint: disable=cyclic-import 567 from bauiv1lib.account.signin import show_sign_in_prompt 568 from bauiv1lib.coop.browser import CoopBrowserWindow 569 570 # no-op if we're not currently in control. 571 if not self.main_window_has_control(): 572 return 573 574 plus = bui.app.plus 575 assert plus is not None 576 577 if plus.get_v1_account_state() != 'signed_in': 578 show_sign_in_prompt() 579 return 580 581 self.main_window_replace( 582 CoopBrowserWindow(origin_widget=self._coop_button) 583 ) 584 585 def _team_tourney(self) -> None: 586 # pylint: disable=cyclic-import 587 from bauiv1lib.playlist.browser import PlaylistBrowserWindow 588 589 # no-op if we're not currently in control. 590 if not self.main_window_has_control(): 591 return 592 593 self.main_window_replace( 594 PlaylistBrowserWindow( 595 origin_widget=self._teams_button, 596 sessiontype=bs.DualTeamSession, 597 playlist_select_context=self._playlist_select_context, 598 ) 599 ) 600 601 def _free_for_all(self) -> None: 602 # pylint: disable=cyclic-import 603 from bauiv1lib.playlist.browser import PlaylistBrowserWindow 604 605 # no-op if we're not currently in control. 606 if not self.main_window_has_control(): 607 return 608 609 self.main_window_replace( 610 PlaylistBrowserWindow( 611 origin_widget=self._free_for_all_button, 612 sessiontype=bs.FreeForAllSession, 613 playlist_select_context=self._playlist_select_context, 614 ) 615 ) 616 617 def _draw_dude( 618 self, 619 i: int, 620 btn: bui.Widget, 621 hoffs: float, 622 v: float, 623 scl: float, 624 position: tuple[float, float], 625 color: tuple[float, float, float], 626 ) -> None: 627 # pylint: disable=too-many-positional-arguments 628 h_extra = -100 629 v_extra = 130 630 eye_color = ( 631 0.7 * 1.0 + 0.3 * color[0], 632 0.7 * 1.0 + 0.3 * color[1], 633 0.7 * 1.0 + 0.3 * color[2], 634 ) 635 if i == 0: 636 bui.imagewidget( 637 parent=self._root_widget, 638 draw_controller=btn, 639 position=( 640 hoffs + scl * (h_extra + position[0]), 641 v + scl * (v_extra + position[1]), 642 ), 643 size=(scl * 60, scl * 80), 644 color=color, 645 texture=self._lineup_tex, 646 mesh_transparent=self._lineup_1_transparent_mesh, 647 ) 648 bui.imagewidget( 649 parent=self._root_widget, 650 draw_controller=btn, 651 position=( 652 hoffs + scl * (h_extra + position[0] + 12), 653 v + scl * (v_extra + position[1] + 53), 654 ), 655 size=(scl * 36, scl * 18), 656 texture=self._lineup_tex, 657 color=eye_color, 658 mesh_transparent=self._eyes_mesh, 659 ) 660 elif i == 1: 661 bui.imagewidget( 662 parent=self._root_widget, 663 draw_controller=btn, 664 position=( 665 hoffs + scl * (h_extra + position[0]), 666 v + scl * (v_extra + position[1]), 667 ), 668 size=(scl * 45, scl * 90), 669 color=color, 670 texture=self._lineup_tex, 671 mesh_transparent=self._lineup_2_transparent_mesh, 672 ) 673 bui.imagewidget( 674 parent=self._root_widget, 675 draw_controller=btn, 676 position=( 677 hoffs + scl * (h_extra + position[0] + 5), 678 v + scl * (v_extra + position[1] + 67), 679 ), 680 size=(scl * 32, scl * 16), 681 texture=self._lineup_tex, 682 color=eye_color, 683 mesh_transparent=self._eyes_mesh, 684 ) 685 elif i == 2: 686 bui.imagewidget( 687 parent=self._root_widget, 688 draw_controller=btn, 689 position=( 690 hoffs + scl * (h_extra + position[0]), 691 v + scl * (v_extra + position[1]), 692 ), 693 size=(scl * 45, scl * 90), 694 color=color, 695 texture=self._lineup_tex, 696 mesh_transparent=self._lineup_3_transparent_mesh, 697 ) 698 bui.imagewidget( 699 parent=self._root_widget, 700 draw_controller=btn, 701 position=( 702 hoffs + scl * (h_extra + position[0] + 5), 703 v + scl * (v_extra + position[1] + 59), 704 ), 705 size=(scl * 34, scl * 17), 706 texture=self._lineup_tex, 707 color=eye_color, 708 mesh_transparent=self._eyes_mesh, 709 ) 710 elif i == 3: 711 bui.imagewidget( 712 parent=self._root_widget, 713 draw_controller=btn, 714 position=( 715 hoffs + scl * (h_extra + position[0]), 716 v + scl * (v_extra + position[1]), 717 ), 718 size=(scl * 48, scl * 96), 719 color=color, 720 texture=self._lineup_tex, 721 mesh_transparent=self._lineup_4_transparent_mesh, 722 ) 723 bui.imagewidget( 724 parent=self._root_widget, 725 draw_controller=btn, 726 position=( 727 hoffs + scl * (h_extra + position[0] + 2), 728 v + scl * (v_extra + position[1] + 62), 729 ), 730 size=(scl * 38, scl * 19), 731 texture=self._lineup_tex, 732 color=eye_color, 733 mesh_transparent=self._eyes_mesh, 734 ) 735 736 def _save_state(self) -> None: 737 try: 738 sel = self._root_widget.get_selected_child() 739 if sel == self._teams_button: 740 sel_name = 'Team Games' 741 elif self._coop_button is not None and sel == self._coop_button: 742 sel_name = 'Co-op Games' 743 elif sel == self._free_for_all_button: 744 sel_name = 'Free-for-All Games' 745 elif sel == self._back_button: 746 sel_name = 'Back' 747 else: 748 raise ValueError(f'unrecognized selection {sel}') 749 assert bui.app.classic is not None 750 bui.app.ui_v1.window_states[type(self)] = sel_name 751 except Exception: 752 logging.exception('Error saving state for %s.', self) 753 754 def _restore_state(self) -> None: 755 try: 756 assert bui.app.classic is not None 757 sel_name = bui.app.ui_v1.window_states.get(type(self)) 758 if sel_name == 'Team Games': 759 sel = self._teams_button 760 elif sel_name == 'Co-op Games' and self._coop_button is not None: 761 sel = self._coop_button 762 elif sel_name == 'Free-for-All Games': 763 sel = self._free_for_all_button 764 elif sel_name == 'Back' and self._back_button is not None: 765 sel = self._back_button 766 else: 767 sel = ( 768 self._coop_button 769 if self._coop_button is not None 770 else self._teams_button 771 ) 772 bui.containerwidget(edit=self._root_widget, selected_child=sel) 773 except Exception: 774 logging.exception('Error restoring state for %s.', self)
class
PlaylistSelectContext:
18class PlaylistSelectContext: 19 """For using PlayWindow to select a playlist instead of running game.""" 20 21 back_state: MainWindowState | None = None
For using PlayWindow to select a playlist instead of running game.
class
PlayWindow(bauiv1._uitypes.MainWindow):
24class PlayWindow(bui.MainWindow): 25 """Window for selecting overall play type.""" 26 27 def __init__( 28 self, 29 transition: str | None = 'in_right', 30 origin_widget: bui.Widget | None = None, 31 playlist_select_context: PlaylistSelectContext | None = None, 32 ): 33 # pylint: disable=too-many-statements 34 # pylint: disable=too-many-locals 35 36 # Preload some modules we use in a background thread so we won't 37 # have a visual hitch when the user taps them. 38 bui.app.threadpool.submit_no_wait(self._preload_modules) 39 40 classic = bui.app.classic 41 assert classic is not None 42 43 self._playlist_select_context = playlist_select_context 44 45 uiscale = bui.app.ui_v1.uiscale 46 width = 1300 if uiscale is bui.UIScale.SMALL else 1000 47 height = 1000 if uiscale is bui.UIScale.SMALL else 550 48 49 button_width = 400.0 50 button_height = 360.0 51 button_spacing = 3.0 52 53 if origin_widget is not None: 54 55 # Need to store this ourself since we can function as a 56 # non-main window. 57 self._transition_out = 'out_scale' 58 else: 59 self._transition_out = 'out_right' 60 61 self._r = 'playWindow' 62 63 # Do some fancy math to fill all available screen area up to the 64 # size of our backing container. This lets us fit to the exact 65 # screen shape at small ui scale. 66 screensize = bui.get_virtual_screen_size() 67 safesize = bui.get_virtual_safe_area_size() 68 69 # We're a generally widescreen shaped window, so bump our 70 # overall scale up a bit when screen width is wider than safe 71 # bounds to take advantage of the extra space. 72 smallscale = min(1.6, 1.35 * screensize[0] / safesize[0]) 73 74 scale = ( 75 smallscale 76 if uiscale is bui.UIScale.SMALL 77 else 0.9 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_height = min(height - 80, 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 * height + 0.5 * target_height + 30.0 86 87 super().__init__( 88 root_widget=bui.containerwidget( 89 size=(width, height), 90 toolbar_visibility=( 91 'menu_full' 92 if playlist_select_context is None 93 else 'menu_minimal' 94 ), 95 scale=scale, 96 ), 97 transition=transition, 98 origin_widget=origin_widget, 99 # We're affected by screen size only at small ui-scale. 100 refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, 101 ) 102 103 self._back_button: bui.Widget | None 104 if uiscale is bui.UIScale.SMALL: 105 self._back_button = None 106 bui.containerwidget( 107 edit=self._root_widget, 108 on_cancel_call=self.main_window_back, 109 ) 110 else: 111 self._back_button = bui.buttonwidget( 112 parent=self._root_widget, 113 position=(50, yoffs - 100), 114 size=(60, 60), 115 scale=1.1, 116 text_res_scale=1.5, 117 text_scale=1.2, 118 autoselect=True, 119 label=bui.charstr(bui.SpecialChar.BACK), 120 button_type='backSmall', 121 on_activate_call=self.main_window_back, 122 ) 123 bui.containerwidget( 124 edit=self._root_widget, cancel_button=self._back_button 125 ) 126 127 bui.textwidget( 128 parent=self._root_widget, 129 position=( 130 width * 0.5, 131 yoffs - (50 if uiscale is bui.UIScale.SMALL else 70), 132 ), 133 size=(0, 0), 134 text=bui.Lstr( 135 resource=( 136 (f'{self._r}.titleText') 137 if self._playlist_select_context is None 138 else 'playlistsText' 139 ) 140 ), 141 scale=1.2 if uiscale is bui.UIScale.SMALL else 1.7, 142 res_scale=2.0, 143 maxwidth=250, 144 color=bui.app.ui_v1.heading_color, 145 h_align='center', 146 v_align='center', 147 ) 148 149 scl = 0.75 if self._playlist_select_context is None else 0.68 150 v = height * 0.5 - button_height * scl * 0.5 - 20.0 151 clr = (0.6, 0.7, 0.6, 1.0) 152 153 bcount = 3 if self._playlist_select_context is None else 2 154 155 total_b_width = ( 156 bcount * button_width * scl + (bcount - 1) * button_spacing 157 ) 158 hoffs = (width - total_b_width) * 0.5 159 160 self._lineup_tex = bui.gettexture('playerLineup') 161 angry_computer_transparent_mesh = bui.getmesh( 162 'angryComputerTransparent' 163 ) 164 self._lineup_1_transparent_mesh = bui.getmesh( 165 'playerLineup1Transparent' 166 ) 167 self._lineup_2_transparent_mesh = bui.getmesh( 168 'playerLineup2Transparent' 169 ) 170 self._lineup_3_transparent_mesh = bui.getmesh( 171 'playerLineup3Transparent' 172 ) 173 self._lineup_4_transparent_mesh = bui.getmesh( 174 'playerLineup4Transparent' 175 ) 176 self._eyes_mesh = bui.getmesh('plasticEyesTransparent') 177 178 self._coop_button: bui.Widget | None = None 179 180 # Only show coop button in regular variant. 181 if self._playlist_select_context is None: 182 self._coop_button = btn = bui.buttonwidget( 183 parent=self._root_widget, 184 position=(hoffs, v), 185 size=( 186 scl * button_width, 187 scl * button_height, 188 ), 189 extra_touch_border_scale=0.1, 190 autoselect=True, 191 label='', 192 button_type='square', 193 on_activate_call=self._coop, 194 ) 195 196 if uiscale is bui.UIScale.SMALL: 197 bui.widget( 198 edit=btn, 199 left_widget=bui.get_special_widget('back_button'), 200 ) 201 bui.widget( 202 edit=btn, 203 up_widget=bui.get_special_widget('account_button'), 204 ) 205 bui.widget( 206 edit=btn, 207 down_widget=bui.get_special_widget('settings_button'), 208 ) 209 210 self._draw_dude( 211 0, 212 btn, 213 hoffs, 214 v, 215 scl, 216 position=(140, 30), 217 color=(0.72, 0.4, 1.0), 218 ) 219 self._draw_dude( 220 1, 221 btn, 222 hoffs, 223 v, 224 scl, 225 position=(185, 53), 226 color=(0.71, 0.5, 1.0), 227 ) 228 self._draw_dude( 229 2, 230 btn, 231 hoffs, 232 v, 233 scl, 234 position=(220, 27), 235 color=(0.67, 0.44, 1.0), 236 ) 237 self._draw_dude( 238 3, btn, hoffs, v, scl, position=(255, 57), color=(0.7, 0.3, 1.0) 239 ) 240 bui.imagewidget( 241 parent=self._root_widget, 242 draw_controller=btn, 243 position=(hoffs + scl * 230, v + scl * 153), 244 size=(scl * 115, scl * 115), 245 texture=self._lineup_tex, 246 mesh_transparent=angry_computer_transparent_mesh, 247 ) 248 249 bui.textwidget( 250 parent=self._root_widget, 251 draw_controller=btn, 252 position=(hoffs + scl * (-10), v + scl * 95), 253 size=(scl * button_width, scl * 50), 254 text=bui.Lstr( 255 resource='playModes.singlePlayerCoopText', 256 fallback_resource='playModes.coopText', 257 ), 258 maxwidth=scl * button_width * 0.7, 259 res_scale=1.5, 260 h_align='center', 261 v_align='center', 262 color=(0.7, 0.9, 0.7, 1.0), 263 scale=scl * 1.5, 264 ) 265 266 bui.textwidget( 267 parent=self._root_widget, 268 draw_controller=btn, 269 position=(hoffs + scl * (-10), v + (scl * 54)), 270 size=(scl * button_width, scl * 30), 271 text=bui.Lstr(resource=f'{self._r}.oneToFourPlayersText'), 272 h_align='center', 273 v_align='center', 274 scale=0.83 * scl, 275 flatness=1.0, 276 maxwidth=scl * button_width * 0.7, 277 color=clr, 278 ) 279 280 hoffs += scl * button_width + button_spacing 281 282 self._teams_button = btn = bui.buttonwidget( 283 parent=self._root_widget, 284 position=(hoffs, v), 285 size=( 286 scl * button_width, 287 scl * button_height, 288 ), 289 extra_touch_border_scale=0.1, 290 autoselect=True, 291 label='', 292 button_type='square', 293 on_activate_call=self._team_tourney, 294 ) 295 296 xxx = -14 297 self._draw_dude( 298 2, 299 btn, 300 hoffs, 301 v, 302 scl, 303 position=(xxx + 148, 30), 304 color=(0.2, 0.4, 1.0), 305 ) 306 self._draw_dude( 307 3, 308 btn, 309 hoffs, 310 v, 311 scl, 312 position=(xxx + 181, 53), 313 color=(0.3, 0.4, 1.0), 314 ) 315 self._draw_dude( 316 1, 317 btn, 318 hoffs, 319 v, 320 scl, 321 position=(xxx + 216, 33), 322 color=(0.3, 0.5, 1.0), 323 ) 324 self._draw_dude( 325 0, 326 btn, 327 hoffs, 328 v, 329 scl, 330 position=(xxx + 245, 57), 331 color=(0.3, 0.5, 1.0), 332 ) 333 334 xxx = 155 335 self._draw_dude( 336 0, 337 btn, 338 hoffs, 339 v, 340 scl, 341 position=(xxx + 151, 30), 342 color=(1.0, 0.5, 0.4), 343 ) 344 self._draw_dude( 345 1, 346 btn, 347 hoffs, 348 v, 349 scl, 350 position=(xxx + 189, 53), 351 color=(1.0, 0.58, 0.58), 352 ) 353 self._draw_dude( 354 3, 355 btn, 356 hoffs, 357 v, 358 scl, 359 position=(xxx + 223, 27), 360 color=(1.0, 0.5, 0.5), 361 ) 362 self._draw_dude( 363 2, 364 btn, 365 hoffs, 366 v, 367 scl, 368 position=(xxx + 257, 57), 369 color=(1.0, 0.5, 0.5), 370 ) 371 372 bui.textwidget( 373 parent=self._root_widget, 374 draw_controller=btn, 375 position=(hoffs + scl * (-10), v + scl * 95), 376 size=(scl * button_width, scl * 50), 377 text=bui.Lstr( 378 resource='playModes.teamsText', fallback_resource='teamsText' 379 ), 380 res_scale=1.5, 381 maxwidth=scl * button_width * 0.7, 382 h_align='center', 383 v_align='center', 384 color=(0.7, 0.9, 0.7, 1.0), 385 scale=scl * 1.5, 386 ) 387 bui.textwidget( 388 parent=self._root_widget, 389 draw_controller=btn, 390 position=(hoffs + scl * (-10), v + (scl * 54)), 391 size=(scl * button_width, scl * 30), 392 text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'), 393 h_align='center', 394 v_align='center', 395 res_scale=1.5, 396 scale=0.83 * scl, 397 flatness=1.0, 398 maxwidth=scl * button_width * 0.7, 399 color=clr, 400 ) 401 402 hoffs += scl * button_width + button_spacing 403 self._free_for_all_button = btn = bui.buttonwidget( 404 parent=self._root_widget, 405 position=(hoffs, v), 406 size=(scl * button_width, scl * button_height), 407 extra_touch_border_scale=0.1, 408 autoselect=True, 409 label='', 410 button_type='square', 411 on_activate_call=self._free_for_all, 412 ) 413 414 xxx = -5 415 self._draw_dude( 416 0, 417 btn, 418 hoffs, 419 v, 420 scl, 421 position=(xxx + 140, 30), 422 color=(0.4, 1.0, 0.4), 423 ) 424 self._draw_dude( 425 3, 426 btn, 427 hoffs, 428 v, 429 scl, 430 position=(xxx + 185, 53), 431 color=(1.0, 0.4, 0.5), 432 ) 433 self._draw_dude( 434 1, 435 btn, 436 hoffs, 437 v, 438 scl, 439 position=(xxx + 220, 27), 440 color=(0.4, 0.5, 1.0), 441 ) 442 self._draw_dude( 443 2, 444 btn, 445 hoffs, 446 v, 447 scl, 448 position=(xxx + 255, 57), 449 color=(0.5, 1.0, 0.4), 450 ) 451 xxx = 140 452 self._draw_dude( 453 2, 454 btn, 455 hoffs, 456 v, 457 scl, 458 position=(xxx + 148, 30), 459 color=(1.0, 0.9, 0.4), 460 ) 461 self._draw_dude( 462 0, 463 btn, 464 hoffs, 465 v, 466 scl, 467 position=(xxx + 182, 53), 468 color=(0.7, 1.0, 0.5), 469 ) 470 self._draw_dude( 471 3, 472 btn, 473 hoffs, 474 v, 475 scl, 476 position=(xxx + 233, 27), 477 color=(0.7, 0.5, 0.9), 478 ) 479 self._draw_dude( 480 1, 481 btn, 482 hoffs, 483 v, 484 scl, 485 position=(xxx + 266, 53), 486 color=(0.4, 0.5, 0.8), 487 ) 488 bui.textwidget( 489 parent=self._root_widget, 490 draw_controller=btn, 491 position=(hoffs + scl * (-10), v + scl * 95), 492 size=(scl * button_width, scl * 50), 493 text=bui.Lstr( 494 resource='playModes.freeForAllText', 495 fallback_resource='freeForAllText', 496 ), 497 maxwidth=scl * button_width * 0.7, 498 h_align='center', 499 v_align='center', 500 color=(0.7, 0.9, 0.7, 1.0), 501 scale=scl * 1.5, 502 ) 503 bui.textwidget( 504 parent=self._root_widget, 505 draw_controller=btn, 506 position=(hoffs + scl * (-10), v + (scl * 54)), 507 size=(scl * button_width, scl * 30), 508 text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'), 509 h_align='center', 510 v_align='center', 511 scale=0.83 * scl, 512 flatness=1.0, 513 maxwidth=scl * button_width * 0.7, 514 color=clr, 515 ) 516 517 if uiscale is bui.UIScale.SMALL: 518 bui.containerwidget( 519 edit=self._root_widget, 520 selected_child=( 521 self._coop_button 522 if self._playlist_select_context is None 523 else self._teams_button 524 ), 525 ) 526 else: 527 bui.containerwidget( 528 edit=self._root_widget, 529 selected_child=( 530 self._coop_button 531 if self._playlist_select_context is None 532 else self._teams_button 533 ), 534 ) 535 536 self._restore_state() 537 538 @override 539 def get_main_window_state(self) -> bui.MainWindowState: 540 # Support recreating our window for back/refresh purposes. 541 cls = type(self) 542 543 # Pull any values out of self here; if we do it in the lambda 544 # we'll keep our window alive inadvertantly. 545 playlist_select_context = self._playlist_select_context 546 return bui.BasicMainWindowState( 547 create_call=lambda transition, origin_widget: cls( 548 transition=transition, 549 origin_widget=origin_widget, 550 playlist_select_context=playlist_select_context, 551 ) 552 ) 553 554 @override 555 def on_main_window_close(self) -> None: 556 self._save_state() 557 558 @staticmethod 559 def _preload_modules() -> None: 560 """Preload modules we use; avoids hitches (called in bg thread).""" 561 import bauiv1lib.mainmenu as _unused1 562 import bauiv1lib.account as _unused2 563 import bauiv1lib.coop.browser as _unused3 564 import bauiv1lib.playlist.browser as _unused4 565 566 def _coop(self) -> None: 567 # pylint: disable=cyclic-import 568 from bauiv1lib.account.signin import show_sign_in_prompt 569 from bauiv1lib.coop.browser import CoopBrowserWindow 570 571 # no-op if we're not currently in control. 572 if not self.main_window_has_control(): 573 return 574 575 plus = bui.app.plus 576 assert plus is not None 577 578 if plus.get_v1_account_state() != 'signed_in': 579 show_sign_in_prompt() 580 return 581 582 self.main_window_replace( 583 CoopBrowserWindow(origin_widget=self._coop_button) 584 ) 585 586 def _team_tourney(self) -> None: 587 # pylint: disable=cyclic-import 588 from bauiv1lib.playlist.browser import PlaylistBrowserWindow 589 590 # no-op if we're not currently in control. 591 if not self.main_window_has_control(): 592 return 593 594 self.main_window_replace( 595 PlaylistBrowserWindow( 596 origin_widget=self._teams_button, 597 sessiontype=bs.DualTeamSession, 598 playlist_select_context=self._playlist_select_context, 599 ) 600 ) 601 602 def _free_for_all(self) -> None: 603 # pylint: disable=cyclic-import 604 from bauiv1lib.playlist.browser import PlaylistBrowserWindow 605 606 # no-op if we're not currently in control. 607 if not self.main_window_has_control(): 608 return 609 610 self.main_window_replace( 611 PlaylistBrowserWindow( 612 origin_widget=self._free_for_all_button, 613 sessiontype=bs.FreeForAllSession, 614 playlist_select_context=self._playlist_select_context, 615 ) 616 ) 617 618 def _draw_dude( 619 self, 620 i: int, 621 btn: bui.Widget, 622 hoffs: float, 623 v: float, 624 scl: float, 625 position: tuple[float, float], 626 color: tuple[float, float, float], 627 ) -> None: 628 # pylint: disable=too-many-positional-arguments 629 h_extra = -100 630 v_extra = 130 631 eye_color = ( 632 0.7 * 1.0 + 0.3 * color[0], 633 0.7 * 1.0 + 0.3 * color[1], 634 0.7 * 1.0 + 0.3 * color[2], 635 ) 636 if i == 0: 637 bui.imagewidget( 638 parent=self._root_widget, 639 draw_controller=btn, 640 position=( 641 hoffs + scl * (h_extra + position[0]), 642 v + scl * (v_extra + position[1]), 643 ), 644 size=(scl * 60, scl * 80), 645 color=color, 646 texture=self._lineup_tex, 647 mesh_transparent=self._lineup_1_transparent_mesh, 648 ) 649 bui.imagewidget( 650 parent=self._root_widget, 651 draw_controller=btn, 652 position=( 653 hoffs + scl * (h_extra + position[0] + 12), 654 v + scl * (v_extra + position[1] + 53), 655 ), 656 size=(scl * 36, scl * 18), 657 texture=self._lineup_tex, 658 color=eye_color, 659 mesh_transparent=self._eyes_mesh, 660 ) 661 elif i == 1: 662 bui.imagewidget( 663 parent=self._root_widget, 664 draw_controller=btn, 665 position=( 666 hoffs + scl * (h_extra + position[0]), 667 v + scl * (v_extra + position[1]), 668 ), 669 size=(scl * 45, scl * 90), 670 color=color, 671 texture=self._lineup_tex, 672 mesh_transparent=self._lineup_2_transparent_mesh, 673 ) 674 bui.imagewidget( 675 parent=self._root_widget, 676 draw_controller=btn, 677 position=( 678 hoffs + scl * (h_extra + position[0] + 5), 679 v + scl * (v_extra + position[1] + 67), 680 ), 681 size=(scl * 32, scl * 16), 682 texture=self._lineup_tex, 683 color=eye_color, 684 mesh_transparent=self._eyes_mesh, 685 ) 686 elif i == 2: 687 bui.imagewidget( 688 parent=self._root_widget, 689 draw_controller=btn, 690 position=( 691 hoffs + scl * (h_extra + position[0]), 692 v + scl * (v_extra + position[1]), 693 ), 694 size=(scl * 45, scl * 90), 695 color=color, 696 texture=self._lineup_tex, 697 mesh_transparent=self._lineup_3_transparent_mesh, 698 ) 699 bui.imagewidget( 700 parent=self._root_widget, 701 draw_controller=btn, 702 position=( 703 hoffs + scl * (h_extra + position[0] + 5), 704 v + scl * (v_extra + position[1] + 59), 705 ), 706 size=(scl * 34, scl * 17), 707 texture=self._lineup_tex, 708 color=eye_color, 709 mesh_transparent=self._eyes_mesh, 710 ) 711 elif i == 3: 712 bui.imagewidget( 713 parent=self._root_widget, 714 draw_controller=btn, 715 position=( 716 hoffs + scl * (h_extra + position[0]), 717 v + scl * (v_extra + position[1]), 718 ), 719 size=(scl * 48, scl * 96), 720 color=color, 721 texture=self._lineup_tex, 722 mesh_transparent=self._lineup_4_transparent_mesh, 723 ) 724 bui.imagewidget( 725 parent=self._root_widget, 726 draw_controller=btn, 727 position=( 728 hoffs + scl * (h_extra + position[0] + 2), 729 v + scl * (v_extra + position[1] + 62), 730 ), 731 size=(scl * 38, scl * 19), 732 texture=self._lineup_tex, 733 color=eye_color, 734 mesh_transparent=self._eyes_mesh, 735 ) 736 737 def _save_state(self) -> None: 738 try: 739 sel = self._root_widget.get_selected_child() 740 if sel == self._teams_button: 741 sel_name = 'Team Games' 742 elif self._coop_button is not None and sel == self._coop_button: 743 sel_name = 'Co-op Games' 744 elif sel == self._free_for_all_button: 745 sel_name = 'Free-for-All Games' 746 elif sel == self._back_button: 747 sel_name = 'Back' 748 else: 749 raise ValueError(f'unrecognized selection {sel}') 750 assert bui.app.classic is not None 751 bui.app.ui_v1.window_states[type(self)] = sel_name 752 except Exception: 753 logging.exception('Error saving state for %s.', self) 754 755 def _restore_state(self) -> None: 756 try: 757 assert bui.app.classic is not None 758 sel_name = bui.app.ui_v1.window_states.get(type(self)) 759 if sel_name == 'Team Games': 760 sel = self._teams_button 761 elif sel_name == 'Co-op Games' and self._coop_button is not None: 762 sel = self._coop_button 763 elif sel_name == 'Free-for-All Games': 764 sel = self._free_for_all_button 765 elif sel_name == 'Back' and self._back_button is not None: 766 sel = self._back_button 767 else: 768 sel = ( 769 self._coop_button 770 if self._coop_button is not None 771 else self._teams_button 772 ) 773 bui.containerwidget(edit=self._root_widget, selected_child=sel) 774 except Exception: 775 logging.exception('Error restoring state for %s.', self)
Window for selecting overall play type.
PlayWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None, playlist_select_context: PlaylistSelectContext | None = None)
27 def __init__( 28 self, 29 transition: str | None = 'in_right', 30 origin_widget: bui.Widget | None = None, 31 playlist_select_context: PlaylistSelectContext | None = None, 32 ): 33 # pylint: disable=too-many-statements 34 # pylint: disable=too-many-locals 35 36 # Preload some modules we use in a background thread so we won't 37 # have a visual hitch when the user taps them. 38 bui.app.threadpool.submit_no_wait(self._preload_modules) 39 40 classic = bui.app.classic 41 assert classic is not None 42 43 self._playlist_select_context = playlist_select_context 44 45 uiscale = bui.app.ui_v1.uiscale 46 width = 1300 if uiscale is bui.UIScale.SMALL else 1000 47 height = 1000 if uiscale is bui.UIScale.SMALL else 550 48 49 button_width = 400.0 50 button_height = 360.0 51 button_spacing = 3.0 52 53 if origin_widget is not None: 54 55 # Need to store this ourself since we can function as a 56 # non-main window. 57 self._transition_out = 'out_scale' 58 else: 59 self._transition_out = 'out_right' 60 61 self._r = 'playWindow' 62 63 # Do some fancy math to fill all available screen area up to the 64 # size of our backing container. This lets us fit to the exact 65 # screen shape at small ui scale. 66 screensize = bui.get_virtual_screen_size() 67 safesize = bui.get_virtual_safe_area_size() 68 69 # We're a generally widescreen shaped window, so bump our 70 # overall scale up a bit when screen width is wider than safe 71 # bounds to take advantage of the extra space. 72 smallscale = min(1.6, 1.35 * screensize[0] / safesize[0]) 73 74 scale = ( 75 smallscale 76 if uiscale is bui.UIScale.SMALL 77 else 0.9 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_height = min(height - 80, 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 * height + 0.5 * target_height + 30.0 86 87 super().__init__( 88 root_widget=bui.containerwidget( 89 size=(width, height), 90 toolbar_visibility=( 91 'menu_full' 92 if playlist_select_context is None 93 else 'menu_minimal' 94 ), 95 scale=scale, 96 ), 97 transition=transition, 98 origin_widget=origin_widget, 99 # We're affected by screen size only at small ui-scale. 100 refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, 101 ) 102 103 self._back_button: bui.Widget | None 104 if uiscale is bui.UIScale.SMALL: 105 self._back_button = None 106 bui.containerwidget( 107 edit=self._root_widget, 108 on_cancel_call=self.main_window_back, 109 ) 110 else: 111 self._back_button = bui.buttonwidget( 112 parent=self._root_widget, 113 position=(50, yoffs - 100), 114 size=(60, 60), 115 scale=1.1, 116 text_res_scale=1.5, 117 text_scale=1.2, 118 autoselect=True, 119 label=bui.charstr(bui.SpecialChar.BACK), 120 button_type='backSmall', 121 on_activate_call=self.main_window_back, 122 ) 123 bui.containerwidget( 124 edit=self._root_widget, cancel_button=self._back_button 125 ) 126 127 bui.textwidget( 128 parent=self._root_widget, 129 position=( 130 width * 0.5, 131 yoffs - (50 if uiscale is bui.UIScale.SMALL else 70), 132 ), 133 size=(0, 0), 134 text=bui.Lstr( 135 resource=( 136 (f'{self._r}.titleText') 137 if self._playlist_select_context is None 138 else 'playlistsText' 139 ) 140 ), 141 scale=1.2 if uiscale is bui.UIScale.SMALL else 1.7, 142 res_scale=2.0, 143 maxwidth=250, 144 color=bui.app.ui_v1.heading_color, 145 h_align='center', 146 v_align='center', 147 ) 148 149 scl = 0.75 if self._playlist_select_context is None else 0.68 150 v = height * 0.5 - button_height * scl * 0.5 - 20.0 151 clr = (0.6, 0.7, 0.6, 1.0) 152 153 bcount = 3 if self._playlist_select_context is None else 2 154 155 total_b_width = ( 156 bcount * button_width * scl + (bcount - 1) * button_spacing 157 ) 158 hoffs = (width - total_b_width) * 0.5 159 160 self._lineup_tex = bui.gettexture('playerLineup') 161 angry_computer_transparent_mesh = bui.getmesh( 162 'angryComputerTransparent' 163 ) 164 self._lineup_1_transparent_mesh = bui.getmesh( 165 'playerLineup1Transparent' 166 ) 167 self._lineup_2_transparent_mesh = bui.getmesh( 168 'playerLineup2Transparent' 169 ) 170 self._lineup_3_transparent_mesh = bui.getmesh( 171 'playerLineup3Transparent' 172 ) 173 self._lineup_4_transparent_mesh = bui.getmesh( 174 'playerLineup4Transparent' 175 ) 176 self._eyes_mesh = bui.getmesh('plasticEyesTransparent') 177 178 self._coop_button: bui.Widget | None = None 179 180 # Only show coop button in regular variant. 181 if self._playlist_select_context is None: 182 self._coop_button = btn = bui.buttonwidget( 183 parent=self._root_widget, 184 position=(hoffs, v), 185 size=( 186 scl * button_width, 187 scl * button_height, 188 ), 189 extra_touch_border_scale=0.1, 190 autoselect=True, 191 label='', 192 button_type='square', 193 on_activate_call=self._coop, 194 ) 195 196 if uiscale is bui.UIScale.SMALL: 197 bui.widget( 198 edit=btn, 199 left_widget=bui.get_special_widget('back_button'), 200 ) 201 bui.widget( 202 edit=btn, 203 up_widget=bui.get_special_widget('account_button'), 204 ) 205 bui.widget( 206 edit=btn, 207 down_widget=bui.get_special_widget('settings_button'), 208 ) 209 210 self._draw_dude( 211 0, 212 btn, 213 hoffs, 214 v, 215 scl, 216 position=(140, 30), 217 color=(0.72, 0.4, 1.0), 218 ) 219 self._draw_dude( 220 1, 221 btn, 222 hoffs, 223 v, 224 scl, 225 position=(185, 53), 226 color=(0.71, 0.5, 1.0), 227 ) 228 self._draw_dude( 229 2, 230 btn, 231 hoffs, 232 v, 233 scl, 234 position=(220, 27), 235 color=(0.67, 0.44, 1.0), 236 ) 237 self._draw_dude( 238 3, btn, hoffs, v, scl, position=(255, 57), color=(0.7, 0.3, 1.0) 239 ) 240 bui.imagewidget( 241 parent=self._root_widget, 242 draw_controller=btn, 243 position=(hoffs + scl * 230, v + scl * 153), 244 size=(scl * 115, scl * 115), 245 texture=self._lineup_tex, 246 mesh_transparent=angry_computer_transparent_mesh, 247 ) 248 249 bui.textwidget( 250 parent=self._root_widget, 251 draw_controller=btn, 252 position=(hoffs + scl * (-10), v + scl * 95), 253 size=(scl * button_width, scl * 50), 254 text=bui.Lstr( 255 resource='playModes.singlePlayerCoopText', 256 fallback_resource='playModes.coopText', 257 ), 258 maxwidth=scl * button_width * 0.7, 259 res_scale=1.5, 260 h_align='center', 261 v_align='center', 262 color=(0.7, 0.9, 0.7, 1.0), 263 scale=scl * 1.5, 264 ) 265 266 bui.textwidget( 267 parent=self._root_widget, 268 draw_controller=btn, 269 position=(hoffs + scl * (-10), v + (scl * 54)), 270 size=(scl * button_width, scl * 30), 271 text=bui.Lstr(resource=f'{self._r}.oneToFourPlayersText'), 272 h_align='center', 273 v_align='center', 274 scale=0.83 * scl, 275 flatness=1.0, 276 maxwidth=scl * button_width * 0.7, 277 color=clr, 278 ) 279 280 hoffs += scl * button_width + button_spacing 281 282 self._teams_button = btn = bui.buttonwidget( 283 parent=self._root_widget, 284 position=(hoffs, v), 285 size=( 286 scl * button_width, 287 scl * button_height, 288 ), 289 extra_touch_border_scale=0.1, 290 autoselect=True, 291 label='', 292 button_type='square', 293 on_activate_call=self._team_tourney, 294 ) 295 296 xxx = -14 297 self._draw_dude( 298 2, 299 btn, 300 hoffs, 301 v, 302 scl, 303 position=(xxx + 148, 30), 304 color=(0.2, 0.4, 1.0), 305 ) 306 self._draw_dude( 307 3, 308 btn, 309 hoffs, 310 v, 311 scl, 312 position=(xxx + 181, 53), 313 color=(0.3, 0.4, 1.0), 314 ) 315 self._draw_dude( 316 1, 317 btn, 318 hoffs, 319 v, 320 scl, 321 position=(xxx + 216, 33), 322 color=(0.3, 0.5, 1.0), 323 ) 324 self._draw_dude( 325 0, 326 btn, 327 hoffs, 328 v, 329 scl, 330 position=(xxx + 245, 57), 331 color=(0.3, 0.5, 1.0), 332 ) 333 334 xxx = 155 335 self._draw_dude( 336 0, 337 btn, 338 hoffs, 339 v, 340 scl, 341 position=(xxx + 151, 30), 342 color=(1.0, 0.5, 0.4), 343 ) 344 self._draw_dude( 345 1, 346 btn, 347 hoffs, 348 v, 349 scl, 350 position=(xxx + 189, 53), 351 color=(1.0, 0.58, 0.58), 352 ) 353 self._draw_dude( 354 3, 355 btn, 356 hoffs, 357 v, 358 scl, 359 position=(xxx + 223, 27), 360 color=(1.0, 0.5, 0.5), 361 ) 362 self._draw_dude( 363 2, 364 btn, 365 hoffs, 366 v, 367 scl, 368 position=(xxx + 257, 57), 369 color=(1.0, 0.5, 0.5), 370 ) 371 372 bui.textwidget( 373 parent=self._root_widget, 374 draw_controller=btn, 375 position=(hoffs + scl * (-10), v + scl * 95), 376 size=(scl * button_width, scl * 50), 377 text=bui.Lstr( 378 resource='playModes.teamsText', fallback_resource='teamsText' 379 ), 380 res_scale=1.5, 381 maxwidth=scl * button_width * 0.7, 382 h_align='center', 383 v_align='center', 384 color=(0.7, 0.9, 0.7, 1.0), 385 scale=scl * 1.5, 386 ) 387 bui.textwidget( 388 parent=self._root_widget, 389 draw_controller=btn, 390 position=(hoffs + scl * (-10), v + (scl * 54)), 391 size=(scl * button_width, scl * 30), 392 text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'), 393 h_align='center', 394 v_align='center', 395 res_scale=1.5, 396 scale=0.83 * scl, 397 flatness=1.0, 398 maxwidth=scl * button_width * 0.7, 399 color=clr, 400 ) 401 402 hoffs += scl * button_width + button_spacing 403 self._free_for_all_button = btn = bui.buttonwidget( 404 parent=self._root_widget, 405 position=(hoffs, v), 406 size=(scl * button_width, scl * button_height), 407 extra_touch_border_scale=0.1, 408 autoselect=True, 409 label='', 410 button_type='square', 411 on_activate_call=self._free_for_all, 412 ) 413 414 xxx = -5 415 self._draw_dude( 416 0, 417 btn, 418 hoffs, 419 v, 420 scl, 421 position=(xxx + 140, 30), 422 color=(0.4, 1.0, 0.4), 423 ) 424 self._draw_dude( 425 3, 426 btn, 427 hoffs, 428 v, 429 scl, 430 position=(xxx + 185, 53), 431 color=(1.0, 0.4, 0.5), 432 ) 433 self._draw_dude( 434 1, 435 btn, 436 hoffs, 437 v, 438 scl, 439 position=(xxx + 220, 27), 440 color=(0.4, 0.5, 1.0), 441 ) 442 self._draw_dude( 443 2, 444 btn, 445 hoffs, 446 v, 447 scl, 448 position=(xxx + 255, 57), 449 color=(0.5, 1.0, 0.4), 450 ) 451 xxx = 140 452 self._draw_dude( 453 2, 454 btn, 455 hoffs, 456 v, 457 scl, 458 position=(xxx + 148, 30), 459 color=(1.0, 0.9, 0.4), 460 ) 461 self._draw_dude( 462 0, 463 btn, 464 hoffs, 465 v, 466 scl, 467 position=(xxx + 182, 53), 468 color=(0.7, 1.0, 0.5), 469 ) 470 self._draw_dude( 471 3, 472 btn, 473 hoffs, 474 v, 475 scl, 476 position=(xxx + 233, 27), 477 color=(0.7, 0.5, 0.9), 478 ) 479 self._draw_dude( 480 1, 481 btn, 482 hoffs, 483 v, 484 scl, 485 position=(xxx + 266, 53), 486 color=(0.4, 0.5, 0.8), 487 ) 488 bui.textwidget( 489 parent=self._root_widget, 490 draw_controller=btn, 491 position=(hoffs + scl * (-10), v + scl * 95), 492 size=(scl * button_width, scl * 50), 493 text=bui.Lstr( 494 resource='playModes.freeForAllText', 495 fallback_resource='freeForAllText', 496 ), 497 maxwidth=scl * button_width * 0.7, 498 h_align='center', 499 v_align='center', 500 color=(0.7, 0.9, 0.7, 1.0), 501 scale=scl * 1.5, 502 ) 503 bui.textwidget( 504 parent=self._root_widget, 505 draw_controller=btn, 506 position=(hoffs + scl * (-10), v + (scl * 54)), 507 size=(scl * button_width, scl * 30), 508 text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'), 509 h_align='center', 510 v_align='center', 511 scale=0.83 * scl, 512 flatness=1.0, 513 maxwidth=scl * button_width * 0.7, 514 color=clr, 515 ) 516 517 if uiscale is bui.UIScale.SMALL: 518 bui.containerwidget( 519 edit=self._root_widget, 520 selected_child=( 521 self._coop_button 522 if self._playlist_select_context is None 523 else self._teams_button 524 ), 525 ) 526 else: 527 bui.containerwidget( 528 edit=self._root_widget, 529 selected_child=( 530 self._coop_button 531 if self._playlist_select_context is None 532 else self._teams_button 533 ), 534 ) 535 536 self._restore_state()
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.
538 @override 539 def get_main_window_state(self) -> bui.MainWindowState: 540 # Support recreating our window for back/refresh purposes. 541 cls = type(self) 542 543 # Pull any values out of self here; if we do it in the lambda 544 # we'll keep our window alive inadvertantly. 545 playlist_select_context = self._playlist_select_context 546 return bui.BasicMainWindowState( 547 create_call=lambda transition, origin_widget: cls( 548 transition=transition, 549 origin_widget=origin_widget, 550 playlist_select_context=playlist_select_context, 551 ) 552 )
Return a WindowState to recreate this window, if supported.