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.

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 = 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.

@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.