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

Return a WindowState to recreate this window, if supported.

@override
def on_main_window_close(self) -> None:
572    @override
573    def on_main_window_close(self) -> None:
574        self._save_state()

Called before transitioning out a main window.

A good opportunity to save window state/etc.