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 = 1100 if uiscale is bui.UIScale.SMALL else 1000
 46        x_offs = 150 if uiscale is bui.UIScale.SMALL else 90
 47        y_offs = -60 if uiscale is bui.UIScale.SMALL else 45
 48        height = 650 if uiscale is bui.UIScale.SMALL else 550
 49        button_width = 400
 50
 51        if origin_widget is not None:
 52
 53            # Need to store this ourself since we can function as a
 54            # non-main window.
 55            self._transition_out = 'out_scale'
 56        else:
 57            self._transition_out = 'out_right'
 58
 59        self._r = 'playWindow'
 60
 61        super().__init__(
 62            root_widget=bui.containerwidget(
 63                size=(width, height),
 64                toolbar_visibility=(
 65                    'menu_full'
 66                    if playlist_select_context is None
 67                    else 'menu_minimal'
 68                ),
 69                scale=(
 70                    1.35
 71                    if uiscale is bui.UIScale.SMALL
 72                    else 0.9 if uiscale is bui.UIScale.MEDIUM else 0.8
 73                ),
 74                stack_offset=(
 75                    (0, 20) if uiscale is bui.UIScale.SMALL else (0, 0)
 76                ),
 77            ),
 78            transition=transition,
 79            origin_widget=origin_widget,
 80        )
 81
 82        self._back_button: bui.Widget | None
 83        if uiscale is bui.UIScale.SMALL:
 84            self._back_button = None
 85            bui.containerwidget(
 86                edit=self._root_widget,
 87                on_cancel_call=self.main_window_back,
 88            )
 89        else:
 90            self._back_button = bui.buttonwidget(
 91                parent=self._root_widget,
 92                position=(5 + x_offs, height - 162 + y_offs),
 93                size=(60, 60),
 94                scale=1.1,
 95                text_res_scale=1.5,
 96                text_scale=1.2,
 97                autoselect=True,
 98                label=bui.charstr(bui.SpecialChar.BACK),
 99                button_type='backSmall',
100                on_activate_call=self.main_window_back,
101            )
102            bui.containerwidget(
103                edit=self._root_widget, cancel_button=self._back_button
104            )
105
106        bui.textwidget(
107            parent=self._root_widget,
108            position=(
109                width * 0.5,
110                height - (83 if uiscale is bui.UIScale.SMALL else 131) + y_offs,
111            ),
112            size=(0, 0),
113            text=bui.Lstr(
114                resource=(
115                    (f'{self._r}.titleText')
116                    if self._playlist_select_context is None
117                    else 'playlistsText'
118                )
119            ),
120            scale=1.2 if uiscale is bui.UIScale.SMALL else 1.7,
121            res_scale=2.0,
122            maxwidth=400,
123            color=bui.app.ui_v1.heading_color,
124            h_align='center',
125            v_align='center',
126        )
127        v = (
128            height
129            - (110 if self._playlist_select_context is None else 90)
130            + y_offs
131        )
132        v -= 100
133        clr = (0.6, 0.7, 0.6, 1.0)
134        v -= 270 if self._playlist_select_context is None else 280
135        v += 65 if uiscale is bui.UIScale.SMALL else 0
136        hoffs = (
137            x_offs - 45
138            if self._playlist_select_context is None
139            else x_offs - 100
140        )
141        scl = 0.75 if self._playlist_select_context is None else 0.68
142
143        self._lineup_tex = bui.gettexture('playerLineup')
144        angry_computer_transparent_mesh = bui.getmesh(
145            'angryComputerTransparent'
146        )
147        self._lineup_1_transparent_mesh = bui.getmesh(
148            'playerLineup1Transparent'
149        )
150        self._lineup_2_transparent_mesh = bui.getmesh(
151            'playerLineup2Transparent'
152        )
153        self._lineup_3_transparent_mesh = bui.getmesh(
154            'playerLineup3Transparent'
155        )
156        self._lineup_4_transparent_mesh = bui.getmesh(
157            'playerLineup4Transparent'
158        )
159        self._eyes_mesh = bui.getmesh('plasticEyesTransparent')
160
161        self._coop_button: bui.Widget | None = None
162
163        # Only show coop button in regular variant.
164        if self._playlist_select_context is None:
165            self._coop_button = btn = bui.buttonwidget(
166                parent=self._root_widget,
167                position=(hoffs, v),
168                size=(
169                    scl * button_width,
170                    scl * 360,
171                ),
172                extra_touch_border_scale=0.1,
173                autoselect=True,
174                label='',
175                button_type='square',
176                on_activate_call=self._coop,
177            )
178
179            if uiscale is bui.UIScale.SMALL:
180                bui.widget(
181                    edit=btn,
182                    left_widget=bui.get_special_widget('back_button'),
183                )
184                bui.widget(
185                    edit=btn,
186                    up_widget=bui.get_special_widget('account_button'),
187                )
188                bui.widget(
189                    edit=btn,
190                    down_widget=bui.get_special_widget('settings_button'),
191                )
192
193            self._draw_dude(
194                0,
195                btn,
196                hoffs,
197                v,
198                scl,
199                position=(140, 30),
200                color=(0.72, 0.4, 1.0),
201            )
202            self._draw_dude(
203                1,
204                btn,
205                hoffs,
206                v,
207                scl,
208                position=(185, 53),
209                color=(0.71, 0.5, 1.0),
210            )
211            self._draw_dude(
212                2,
213                btn,
214                hoffs,
215                v,
216                scl,
217                position=(220, 27),
218                color=(0.67, 0.44, 1.0),
219            )
220            self._draw_dude(
221                3, btn, hoffs, v, scl, position=(255, 57), color=(0.7, 0.3, 1.0)
222            )
223            bui.imagewidget(
224                parent=self._root_widget,
225                draw_controller=btn,
226                position=(hoffs + scl * 230, v + scl * 153),
227                size=(scl * 115, scl * 115),
228                texture=self._lineup_tex,
229                mesh_transparent=angry_computer_transparent_mesh,
230            )
231
232            bui.textwidget(
233                parent=self._root_widget,
234                draw_controller=btn,
235                position=(hoffs + scl * (-10), v + scl * 95),
236                size=(scl * button_width, scl * 50),
237                text=bui.Lstr(
238                    resource='playModes.singlePlayerCoopText',
239                    fallback_resource='playModes.coopText',
240                ),
241                maxwidth=scl * button_width * 0.7,
242                res_scale=1.5,
243                h_align='center',
244                v_align='center',
245                color=(0.7, 0.9, 0.7, 1.0),
246                scale=scl * 1.5,
247            )
248
249            bui.textwidget(
250                parent=self._root_widget,
251                draw_controller=btn,
252                position=(hoffs + scl * (-10), v + (scl * 54)),
253                size=(scl * button_width, scl * 30),
254                text=bui.Lstr(resource=f'{self._r}.oneToFourPlayersText'),
255                h_align='center',
256                v_align='center',
257                scale=0.83 * scl,
258                flatness=1.0,
259                maxwidth=scl * button_width * 0.7,
260                color=clr,
261            )
262
263        scl = 0.75 if self._playlist_select_context is None else 0.68
264        hoffs += 300 if self._playlist_select_context is None else 216
265        # v += 0 if self._playlist_select_context is None else -68
266
267        self._teams_button = btn = bui.buttonwidget(
268            parent=self._root_widget,
269            position=(
270                hoffs,
271                v,
272                # v + (scl * 15 if
273                # self._playlist_select_context is None else 0),
274            ),
275            size=(
276                scl * button_width,
277                scl * (360 if self._playlist_select_context is None else 360),
278            ),
279            extra_touch_border_scale=0.1,
280            autoselect=True,
281            label='',
282            button_type='square',
283            on_activate_call=self._team_tourney,
284        )
285
286        xxx = -14
287        self._draw_dude(
288            2,
289            btn,
290            hoffs,
291            v,
292            scl,
293            position=(xxx + 148, 30),
294            color=(0.2, 0.4, 1.0),
295        )
296        self._draw_dude(
297            3,
298            btn,
299            hoffs,
300            v,
301            scl,
302            position=(xxx + 181, 53),
303            color=(0.3, 0.4, 1.0),
304        )
305        self._draw_dude(
306            1,
307            btn,
308            hoffs,
309            v,
310            scl,
311            position=(xxx + 216, 33),
312            color=(0.3, 0.5, 1.0),
313        )
314        self._draw_dude(
315            0,
316            btn,
317            hoffs,
318            v,
319            scl,
320            position=(xxx + 245, 57),
321            color=(0.3, 0.5, 1.0),
322        )
323
324        xxx = 155
325        self._draw_dude(
326            0,
327            btn,
328            hoffs,
329            v,
330            scl,
331            position=(xxx + 151, 30),
332            color=(1.0, 0.5, 0.4),
333        )
334        self._draw_dude(
335            1,
336            btn,
337            hoffs,
338            v,
339            scl,
340            position=(xxx + 189, 53),
341            color=(1.0, 0.58, 0.58),
342        )
343        self._draw_dude(
344            3,
345            btn,
346            hoffs,
347            v,
348            scl,
349            position=(xxx + 223, 27),
350            color=(1.0, 0.5, 0.5),
351        )
352        self._draw_dude(
353            2,
354            btn,
355            hoffs,
356            v,
357            scl,
358            position=(xxx + 257, 57),
359            color=(1.0, 0.5, 0.5),
360        )
361
362        bui.textwidget(
363            parent=self._root_widget,
364            draw_controller=btn,
365            position=(hoffs + scl * (-10), v + scl * 95),
366            size=(scl * button_width, scl * 50),
367            text=bui.Lstr(
368                resource='playModes.teamsText', fallback_resource='teamsText'
369            ),
370            res_scale=1.5,
371            maxwidth=scl * button_width * 0.7,
372            h_align='center',
373            v_align='center',
374            color=(0.7, 0.9, 0.7, 1.0),
375            scale=scl * 1.5,
376        )
377        bui.textwidget(
378            parent=self._root_widget,
379            draw_controller=btn,
380            position=(hoffs + scl * (-10), v + (scl * 54)),
381            size=(scl * button_width, scl * 30),
382            text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'),
383            h_align='center',
384            v_align='center',
385            res_scale=1.5,
386            scale=0.83 * scl,
387            flatness=1.0,
388            maxwidth=scl * button_width * 0.7,
389            color=clr,
390        )
391
392        hoffs += 300 if self._playlist_select_context is None else 300
393        # v -= 0 if self._playlist_select_context is None else 0
394        self._free_for_all_button = btn = bui.buttonwidget(
395            parent=self._root_widget,
396            position=(
397                hoffs,
398                v,
399                # v + (scl * 15
400                # if self._playlist_select_context is None else 0),
401            ),
402            size=(
403                scl * button_width,
404                scl * (360 if self._playlist_select_context is None else 360),
405            ),
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.

back_state: bauiv1.MainWindowState | None = None
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 = 1100 if uiscale is bui.UIScale.SMALL else 1000
 47        x_offs = 150 if uiscale is bui.UIScale.SMALL else 90
 48        y_offs = -60 if uiscale is bui.UIScale.SMALL else 45
 49        height = 650 if uiscale is bui.UIScale.SMALL else 550
 50        button_width = 400
 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        super().__init__(
 63            root_widget=bui.containerwidget(
 64                size=(width, height),
 65                toolbar_visibility=(
 66                    'menu_full'
 67                    if playlist_select_context is None
 68                    else 'menu_minimal'
 69                ),
 70                scale=(
 71                    1.35
 72                    if uiscale is bui.UIScale.SMALL
 73                    else 0.9 if uiscale is bui.UIScale.MEDIUM else 0.8
 74                ),
 75                stack_offset=(
 76                    (0, 20) if uiscale is bui.UIScale.SMALL else (0, 0)
 77                ),
 78            ),
 79            transition=transition,
 80            origin_widget=origin_widget,
 81        )
 82
 83        self._back_button: bui.Widget | None
 84        if uiscale is bui.UIScale.SMALL:
 85            self._back_button = None
 86            bui.containerwidget(
 87                edit=self._root_widget,
 88                on_cancel_call=self.main_window_back,
 89            )
 90        else:
 91            self._back_button = bui.buttonwidget(
 92                parent=self._root_widget,
 93                position=(5 + x_offs, height - 162 + y_offs),
 94                size=(60, 60),
 95                scale=1.1,
 96                text_res_scale=1.5,
 97                text_scale=1.2,
 98                autoselect=True,
 99                label=bui.charstr(bui.SpecialChar.BACK),
100                button_type='backSmall',
101                on_activate_call=self.main_window_back,
102            )
103            bui.containerwidget(
104                edit=self._root_widget, cancel_button=self._back_button
105            )
106
107        bui.textwidget(
108            parent=self._root_widget,
109            position=(
110                width * 0.5,
111                height - (83 if uiscale is bui.UIScale.SMALL else 131) + y_offs,
112            ),
113            size=(0, 0),
114            text=bui.Lstr(
115                resource=(
116                    (f'{self._r}.titleText')
117                    if self._playlist_select_context is None
118                    else 'playlistsText'
119                )
120            ),
121            scale=1.2 if uiscale is bui.UIScale.SMALL else 1.7,
122            res_scale=2.0,
123            maxwidth=400,
124            color=bui.app.ui_v1.heading_color,
125            h_align='center',
126            v_align='center',
127        )
128        v = (
129            height
130            - (110 if self._playlist_select_context is None else 90)
131            + y_offs
132        )
133        v -= 100
134        clr = (0.6, 0.7, 0.6, 1.0)
135        v -= 270 if self._playlist_select_context is None else 280
136        v += 65 if uiscale is bui.UIScale.SMALL else 0
137        hoffs = (
138            x_offs - 45
139            if self._playlist_select_context is None
140            else x_offs - 100
141        )
142        scl = 0.75 if self._playlist_select_context is None else 0.68
143
144        self._lineup_tex = bui.gettexture('playerLineup')
145        angry_computer_transparent_mesh = bui.getmesh(
146            'angryComputerTransparent'
147        )
148        self._lineup_1_transparent_mesh = bui.getmesh(
149            'playerLineup1Transparent'
150        )
151        self._lineup_2_transparent_mesh = bui.getmesh(
152            'playerLineup2Transparent'
153        )
154        self._lineup_3_transparent_mesh = bui.getmesh(
155            'playerLineup3Transparent'
156        )
157        self._lineup_4_transparent_mesh = bui.getmesh(
158            'playerLineup4Transparent'
159        )
160        self._eyes_mesh = bui.getmesh('plasticEyesTransparent')
161
162        self._coop_button: bui.Widget | None = None
163
164        # Only show coop button in regular variant.
165        if self._playlist_select_context is None:
166            self._coop_button = btn = bui.buttonwidget(
167                parent=self._root_widget,
168                position=(hoffs, v),
169                size=(
170                    scl * button_width,
171                    scl * 360,
172                ),
173                extra_touch_border_scale=0.1,
174                autoselect=True,
175                label='',
176                button_type='square',
177                on_activate_call=self._coop,
178            )
179
180            if uiscale is bui.UIScale.SMALL:
181                bui.widget(
182                    edit=btn,
183                    left_widget=bui.get_special_widget('back_button'),
184                )
185                bui.widget(
186                    edit=btn,
187                    up_widget=bui.get_special_widget('account_button'),
188                )
189                bui.widget(
190                    edit=btn,
191                    down_widget=bui.get_special_widget('settings_button'),
192                )
193
194            self._draw_dude(
195                0,
196                btn,
197                hoffs,
198                v,
199                scl,
200                position=(140, 30),
201                color=(0.72, 0.4, 1.0),
202            )
203            self._draw_dude(
204                1,
205                btn,
206                hoffs,
207                v,
208                scl,
209                position=(185, 53),
210                color=(0.71, 0.5, 1.0),
211            )
212            self._draw_dude(
213                2,
214                btn,
215                hoffs,
216                v,
217                scl,
218                position=(220, 27),
219                color=(0.67, 0.44, 1.0),
220            )
221            self._draw_dude(
222                3, btn, hoffs, v, scl, position=(255, 57), color=(0.7, 0.3, 1.0)
223            )
224            bui.imagewidget(
225                parent=self._root_widget,
226                draw_controller=btn,
227                position=(hoffs + scl * 230, v + scl * 153),
228                size=(scl * 115, scl * 115),
229                texture=self._lineup_tex,
230                mesh_transparent=angry_computer_transparent_mesh,
231            )
232
233            bui.textwidget(
234                parent=self._root_widget,
235                draw_controller=btn,
236                position=(hoffs + scl * (-10), v + scl * 95),
237                size=(scl * button_width, scl * 50),
238                text=bui.Lstr(
239                    resource='playModes.singlePlayerCoopText',
240                    fallback_resource='playModes.coopText',
241                ),
242                maxwidth=scl * button_width * 0.7,
243                res_scale=1.5,
244                h_align='center',
245                v_align='center',
246                color=(0.7, 0.9, 0.7, 1.0),
247                scale=scl * 1.5,
248            )
249
250            bui.textwidget(
251                parent=self._root_widget,
252                draw_controller=btn,
253                position=(hoffs + scl * (-10), v + (scl * 54)),
254                size=(scl * button_width, scl * 30),
255                text=bui.Lstr(resource=f'{self._r}.oneToFourPlayersText'),
256                h_align='center',
257                v_align='center',
258                scale=0.83 * scl,
259                flatness=1.0,
260                maxwidth=scl * button_width * 0.7,
261                color=clr,
262            )
263
264        scl = 0.75 if self._playlist_select_context is None else 0.68
265        hoffs += 300 if self._playlist_select_context is None else 216
266        # v += 0 if self._playlist_select_context is None else -68
267
268        self._teams_button = btn = bui.buttonwidget(
269            parent=self._root_widget,
270            position=(
271                hoffs,
272                v,
273                # v + (scl * 15 if
274                # self._playlist_select_context is None else 0),
275            ),
276            size=(
277                scl * button_width,
278                scl * (360 if self._playlist_select_context is None else 360),
279            ),
280            extra_touch_border_scale=0.1,
281            autoselect=True,
282            label='',
283            button_type='square',
284            on_activate_call=self._team_tourney,
285        )
286
287        xxx = -14
288        self._draw_dude(
289            2,
290            btn,
291            hoffs,
292            v,
293            scl,
294            position=(xxx + 148, 30),
295            color=(0.2, 0.4, 1.0),
296        )
297        self._draw_dude(
298            3,
299            btn,
300            hoffs,
301            v,
302            scl,
303            position=(xxx + 181, 53),
304            color=(0.3, 0.4, 1.0),
305        )
306        self._draw_dude(
307            1,
308            btn,
309            hoffs,
310            v,
311            scl,
312            position=(xxx + 216, 33),
313            color=(0.3, 0.5, 1.0),
314        )
315        self._draw_dude(
316            0,
317            btn,
318            hoffs,
319            v,
320            scl,
321            position=(xxx + 245, 57),
322            color=(0.3, 0.5, 1.0),
323        )
324
325        xxx = 155
326        self._draw_dude(
327            0,
328            btn,
329            hoffs,
330            v,
331            scl,
332            position=(xxx + 151, 30),
333            color=(1.0, 0.5, 0.4),
334        )
335        self._draw_dude(
336            1,
337            btn,
338            hoffs,
339            v,
340            scl,
341            position=(xxx + 189, 53),
342            color=(1.0, 0.58, 0.58),
343        )
344        self._draw_dude(
345            3,
346            btn,
347            hoffs,
348            v,
349            scl,
350            position=(xxx + 223, 27),
351            color=(1.0, 0.5, 0.5),
352        )
353        self._draw_dude(
354            2,
355            btn,
356            hoffs,
357            v,
358            scl,
359            position=(xxx + 257, 57),
360            color=(1.0, 0.5, 0.5),
361        )
362
363        bui.textwidget(
364            parent=self._root_widget,
365            draw_controller=btn,
366            position=(hoffs + scl * (-10), v + scl * 95),
367            size=(scl * button_width, scl * 50),
368            text=bui.Lstr(
369                resource='playModes.teamsText', fallback_resource='teamsText'
370            ),
371            res_scale=1.5,
372            maxwidth=scl * button_width * 0.7,
373            h_align='center',
374            v_align='center',
375            color=(0.7, 0.9, 0.7, 1.0),
376            scale=scl * 1.5,
377        )
378        bui.textwidget(
379            parent=self._root_widget,
380            draw_controller=btn,
381            position=(hoffs + scl * (-10), v + (scl * 54)),
382            size=(scl * button_width, scl * 30),
383            text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'),
384            h_align='center',
385            v_align='center',
386            res_scale=1.5,
387            scale=0.83 * scl,
388            flatness=1.0,
389            maxwidth=scl * button_width * 0.7,
390            color=clr,
391        )
392
393        hoffs += 300 if self._playlist_select_context is None else 300
394        # v -= 0 if self._playlist_select_context is None else 0
395        self._free_for_all_button = btn = bui.buttonwidget(
396            parent=self._root_widget,
397            position=(
398                hoffs,
399                v,
400                # v + (scl * 15
401                # if self._playlist_select_context is None else 0),
402            ),
403            size=(
404                scl * button_width,
405                scl * (360 if self._playlist_select_context is None else 360),
406            ),
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 = 1100 if uiscale is bui.UIScale.SMALL else 1000
 47        x_offs = 150 if uiscale is bui.UIScale.SMALL else 90
 48        y_offs = -60 if uiscale is bui.UIScale.SMALL else 45
 49        height = 650 if uiscale is bui.UIScale.SMALL else 550
 50        button_width = 400
 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        super().__init__(
 63            root_widget=bui.containerwidget(
 64                size=(width, height),
 65                toolbar_visibility=(
 66                    'menu_full'
 67                    if playlist_select_context is None
 68                    else 'menu_minimal'
 69                ),
 70                scale=(
 71                    1.35
 72                    if uiscale is bui.UIScale.SMALL
 73                    else 0.9 if uiscale is bui.UIScale.MEDIUM else 0.8
 74                ),
 75                stack_offset=(
 76                    (0, 20) if uiscale is bui.UIScale.SMALL else (0, 0)
 77                ),
 78            ),
 79            transition=transition,
 80            origin_widget=origin_widget,
 81        )
 82
 83        self._back_button: bui.Widget | None
 84        if uiscale is bui.UIScale.SMALL:
 85            self._back_button = None
 86            bui.containerwidget(
 87                edit=self._root_widget,
 88                on_cancel_call=self.main_window_back,
 89            )
 90        else:
 91            self._back_button = bui.buttonwidget(
 92                parent=self._root_widget,
 93                position=(5 + x_offs, height - 162 + y_offs),
 94                size=(60, 60),
 95                scale=1.1,
 96                text_res_scale=1.5,
 97                text_scale=1.2,
 98                autoselect=True,
 99                label=bui.charstr(bui.SpecialChar.BACK),
100                button_type='backSmall',
101                on_activate_call=self.main_window_back,
102            )
103            bui.containerwidget(
104                edit=self._root_widget, cancel_button=self._back_button
105            )
106
107        bui.textwidget(
108            parent=self._root_widget,
109            position=(
110                width * 0.5,
111                height - (83 if uiscale is bui.UIScale.SMALL else 131) + y_offs,
112            ),
113            size=(0, 0),
114            text=bui.Lstr(
115                resource=(
116                    (f'{self._r}.titleText')
117                    if self._playlist_select_context is None
118                    else 'playlistsText'
119                )
120            ),
121            scale=1.2 if uiscale is bui.UIScale.SMALL else 1.7,
122            res_scale=2.0,
123            maxwidth=400,
124            color=bui.app.ui_v1.heading_color,
125            h_align='center',
126            v_align='center',
127        )
128        v = (
129            height
130            - (110 if self._playlist_select_context is None else 90)
131            + y_offs
132        )
133        v -= 100
134        clr = (0.6, 0.7, 0.6, 1.0)
135        v -= 270 if self._playlist_select_context is None else 280
136        v += 65 if uiscale is bui.UIScale.SMALL else 0
137        hoffs = (
138            x_offs - 45
139            if self._playlist_select_context is None
140            else x_offs - 100
141        )
142        scl = 0.75 if self._playlist_select_context is None else 0.68
143
144        self._lineup_tex = bui.gettexture('playerLineup')
145        angry_computer_transparent_mesh = bui.getmesh(
146            'angryComputerTransparent'
147        )
148        self._lineup_1_transparent_mesh = bui.getmesh(
149            'playerLineup1Transparent'
150        )
151        self._lineup_2_transparent_mesh = bui.getmesh(
152            'playerLineup2Transparent'
153        )
154        self._lineup_3_transparent_mesh = bui.getmesh(
155            'playerLineup3Transparent'
156        )
157        self._lineup_4_transparent_mesh = bui.getmesh(
158            'playerLineup4Transparent'
159        )
160        self._eyes_mesh = bui.getmesh('plasticEyesTransparent')
161
162        self._coop_button: bui.Widget | None = None
163
164        # Only show coop button in regular variant.
165        if self._playlist_select_context is None:
166            self._coop_button = btn = bui.buttonwidget(
167                parent=self._root_widget,
168                position=(hoffs, v),
169                size=(
170                    scl * button_width,
171                    scl * 360,
172                ),
173                extra_touch_border_scale=0.1,
174                autoselect=True,
175                label='',
176                button_type='square',
177                on_activate_call=self._coop,
178            )
179
180            if uiscale is bui.UIScale.SMALL:
181                bui.widget(
182                    edit=btn,
183                    left_widget=bui.get_special_widget('back_button'),
184                )
185                bui.widget(
186                    edit=btn,
187                    up_widget=bui.get_special_widget('account_button'),
188                )
189                bui.widget(
190                    edit=btn,
191                    down_widget=bui.get_special_widget('settings_button'),
192                )
193
194            self._draw_dude(
195                0,
196                btn,
197                hoffs,
198                v,
199                scl,
200                position=(140, 30),
201                color=(0.72, 0.4, 1.0),
202            )
203            self._draw_dude(
204                1,
205                btn,
206                hoffs,
207                v,
208                scl,
209                position=(185, 53),
210                color=(0.71, 0.5, 1.0),
211            )
212            self._draw_dude(
213                2,
214                btn,
215                hoffs,
216                v,
217                scl,
218                position=(220, 27),
219                color=(0.67, 0.44, 1.0),
220            )
221            self._draw_dude(
222                3, btn, hoffs, v, scl, position=(255, 57), color=(0.7, 0.3, 1.0)
223            )
224            bui.imagewidget(
225                parent=self._root_widget,
226                draw_controller=btn,
227                position=(hoffs + scl * 230, v + scl * 153),
228                size=(scl * 115, scl * 115),
229                texture=self._lineup_tex,
230                mesh_transparent=angry_computer_transparent_mesh,
231            )
232
233            bui.textwidget(
234                parent=self._root_widget,
235                draw_controller=btn,
236                position=(hoffs + scl * (-10), v + scl * 95),
237                size=(scl * button_width, scl * 50),
238                text=bui.Lstr(
239                    resource='playModes.singlePlayerCoopText',
240                    fallback_resource='playModes.coopText',
241                ),
242                maxwidth=scl * button_width * 0.7,
243                res_scale=1.5,
244                h_align='center',
245                v_align='center',
246                color=(0.7, 0.9, 0.7, 1.0),
247                scale=scl * 1.5,
248            )
249
250            bui.textwidget(
251                parent=self._root_widget,
252                draw_controller=btn,
253                position=(hoffs + scl * (-10), v + (scl * 54)),
254                size=(scl * button_width, scl * 30),
255                text=bui.Lstr(resource=f'{self._r}.oneToFourPlayersText'),
256                h_align='center',
257                v_align='center',
258                scale=0.83 * scl,
259                flatness=1.0,
260                maxwidth=scl * button_width * 0.7,
261                color=clr,
262            )
263
264        scl = 0.75 if self._playlist_select_context is None else 0.68
265        hoffs += 300 if self._playlist_select_context is None else 216
266        # v += 0 if self._playlist_select_context is None else -68
267
268        self._teams_button = btn = bui.buttonwidget(
269            parent=self._root_widget,
270            position=(
271                hoffs,
272                v,
273                # v + (scl * 15 if
274                # self._playlist_select_context is None else 0),
275            ),
276            size=(
277                scl * button_width,
278                scl * (360 if self._playlist_select_context is None else 360),
279            ),
280            extra_touch_border_scale=0.1,
281            autoselect=True,
282            label='',
283            button_type='square',
284            on_activate_call=self._team_tourney,
285        )
286
287        xxx = -14
288        self._draw_dude(
289            2,
290            btn,
291            hoffs,
292            v,
293            scl,
294            position=(xxx + 148, 30),
295            color=(0.2, 0.4, 1.0),
296        )
297        self._draw_dude(
298            3,
299            btn,
300            hoffs,
301            v,
302            scl,
303            position=(xxx + 181, 53),
304            color=(0.3, 0.4, 1.0),
305        )
306        self._draw_dude(
307            1,
308            btn,
309            hoffs,
310            v,
311            scl,
312            position=(xxx + 216, 33),
313            color=(0.3, 0.5, 1.0),
314        )
315        self._draw_dude(
316            0,
317            btn,
318            hoffs,
319            v,
320            scl,
321            position=(xxx + 245, 57),
322            color=(0.3, 0.5, 1.0),
323        )
324
325        xxx = 155
326        self._draw_dude(
327            0,
328            btn,
329            hoffs,
330            v,
331            scl,
332            position=(xxx + 151, 30),
333            color=(1.0, 0.5, 0.4),
334        )
335        self._draw_dude(
336            1,
337            btn,
338            hoffs,
339            v,
340            scl,
341            position=(xxx + 189, 53),
342            color=(1.0, 0.58, 0.58),
343        )
344        self._draw_dude(
345            3,
346            btn,
347            hoffs,
348            v,
349            scl,
350            position=(xxx + 223, 27),
351            color=(1.0, 0.5, 0.5),
352        )
353        self._draw_dude(
354            2,
355            btn,
356            hoffs,
357            v,
358            scl,
359            position=(xxx + 257, 57),
360            color=(1.0, 0.5, 0.5),
361        )
362
363        bui.textwidget(
364            parent=self._root_widget,
365            draw_controller=btn,
366            position=(hoffs + scl * (-10), v + scl * 95),
367            size=(scl * button_width, scl * 50),
368            text=bui.Lstr(
369                resource='playModes.teamsText', fallback_resource='teamsText'
370            ),
371            res_scale=1.5,
372            maxwidth=scl * button_width * 0.7,
373            h_align='center',
374            v_align='center',
375            color=(0.7, 0.9, 0.7, 1.0),
376            scale=scl * 1.5,
377        )
378        bui.textwidget(
379            parent=self._root_widget,
380            draw_controller=btn,
381            position=(hoffs + scl * (-10), v + (scl * 54)),
382            size=(scl * button_width, scl * 30),
383            text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'),
384            h_align='center',
385            v_align='center',
386            res_scale=1.5,
387            scale=0.83 * scl,
388            flatness=1.0,
389            maxwidth=scl * button_width * 0.7,
390            color=clr,
391        )
392
393        hoffs += 300 if self._playlist_select_context is None else 300
394        # v -= 0 if self._playlist_select_context is None else 0
395        self._free_for_all_button = btn = bui.buttonwidget(
396            parent=self._root_widget,
397            position=(
398                hoffs,
399                v,
400                # v + (scl * 15
401                # if self._playlist_select_context is None else 0),
402            ),
403            size=(
404                scl * button_width,
405                scl * (360 if self._playlist_select_context is None else 360),
406            ),
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.

@override
def get_main_window_state(self) -> bauiv1.MainWindowState:
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.

@override
def on_main_window_close(self) -> None:
554    @override
555    def on_main_window_close(self) -> None:
556        self._save_state()

Called before transitioning out a main window.

A good opportunity to save window state/etc.