bauiv1lib.playlist.addgame

Provides a window for selecting a game type to add to a playlist.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides a window for selecting a game type to add to a playlist."""
  4
  5from __future__ import annotations
  6
  7from typing import TYPE_CHECKING, override
  8
  9import bascenev1 as bs
 10import bauiv1 as bui
 11
 12if TYPE_CHECKING:
 13    from bauiv1lib.playlist.editcontroller import PlaylistEditController
 14
 15
 16class PlaylistAddGameWindow(bui.MainWindow):
 17    """Window for selecting a game type to add to a playlist."""
 18
 19    def __init__(
 20        self,
 21        editcontroller: PlaylistEditController,
 22        transition: str | None = 'in_right',
 23        origin_widget: bui.Widget | None = None,
 24    ):
 25        self._editcontroller = editcontroller
 26        self._r = 'addGameWindow'
 27        assert bui.app.classic is not None
 28        uiscale = bui.app.ui_v1.uiscale
 29        self._width = 900 if uiscale is bui.UIScale.SMALL else 650
 30
 31        self._height = (
 32            1200.0
 33            if uiscale is bui.UIScale.SMALL
 34            else 450.0 if uiscale is bui.UIScale.MEDIUM else 500.0
 35        )
 36        self._scroll_width = 210
 37
 38        # Do some fancy math to fill all available screen area up to the
 39        # size of our backing container. This lets us fit to the exact
 40        # screen shape at small ui scale.
 41        screensize = bui.get_virtual_screen_size()
 42        scale = (
 43            2.4
 44            if uiscale is bui.UIScale.SMALL
 45            else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0
 46        )
 47        # Calc screen size in our local container space and clamp to a
 48        # bit smaller than our container size.
 49        target_width = min(self._width - 50, screensize[0] / scale)
 50        target_height = min(self._height - 70, screensize[1] / scale)
 51
 52        # To get top/left coords, go to the center of our window and
 53        # offset by half the width/height of our target area.
 54        yoffs = 0.5 * self._height + 0.5 * target_height + 5.0
 55        x_inset = 0.5 * self._width - 0.5 * target_width
 56
 57        super().__init__(
 58            root_widget=bui.containerwidget(
 59                size=(self._width, self._height),
 60                scale=scale,
 61                toolbar_visibility='menu_minimal',
 62            ),
 63            transition=transition,
 64            origin_widget=origin_widget,
 65            # We're affected by screen size only at small ui-scale.
 66            refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
 67        )
 68
 69        if uiscale is bui.UIScale.SMALL:
 70            self._back_button = bui.get_special_widget('back_button')
 71        else:
 72            self._back_button = bui.buttonwidget(
 73                parent=self._root_widget,
 74                position=(58 + x_inset, yoffs - 53),
 75                size=(60, 48),
 76                label=bui.charstr(bui.SpecialChar.BACK),
 77                autoselect=True,
 78                button_type='backSmall',
 79                on_activate_call=self.main_window_back,
 80            )
 81
 82        self._select_button = select_button = bui.buttonwidget(
 83            parent=self._root_widget,
 84            position=(
 85                x_inset + target_width - 172,
 86                yoffs - 50,
 87            ),
 88            autoselect=True,
 89            size=(160, 60),
 90            scale=0.75,
 91            text_scale=1.2,
 92            label=bui.Lstr(resource='selectText'),
 93            on_activate_call=self._add,
 94        )
 95
 96        bui.widget(
 97            edit=select_button,
 98            right_widget=bui.get_special_widget('squad_button'),
 99        )
100
101        bui.textwidget(
102            parent=self._root_widget,
103            position=(self._width * 0.5, yoffs - 28),
104            size=(0, 0),
105            scale=1.0,
106            text=bui.Lstr(resource=f'{self._r}.titleText'),
107            h_align='center',
108            color=bui.app.ui_v1.title_color,
109            maxwidth=250,
110            v_align='center',
111        )
112        v = yoffs - 64
113
114        self._selected_title_text = bui.textwidget(
115            parent=self._root_widget,
116            position=(x_inset + self._scroll_width + 50 + 30, v - 15),
117            size=(0, 0),
118            scale=1.0,
119            color=(0.7, 1.0, 0.7, 1.0),
120            maxwidth=self._width - self._scroll_width - 150 - x_inset * 2,
121            h_align='left',
122            v_align='center',
123        )
124        v -= 30
125
126        self._selected_description_text = bui.textwidget(
127            parent=self._root_widget,
128            position=(x_inset + self._scroll_width + 50 + 30, v),
129            size=(0, 0),
130            scale=0.7,
131            color=(0.5, 0.8, 0.5, 1.0),
132            maxwidth=self._width - self._scroll_width - 150 - x_inset * 2,
133            h_align='left',
134        )
135
136        scroll_height = target_height - 60
137
138        v = yoffs - 60
139
140        self._scrollwidget = bui.scrollwidget(
141            parent=self._root_widget,
142            position=(x_inset + 61, v - scroll_height),
143            size=(self._scroll_width, scroll_height),
144            highlight=False,
145            border_opacity=0.4,
146        )
147        bui.widget(
148            edit=self._scrollwidget,
149            up_widget=self._back_button,
150            left_widget=self._back_button,
151            right_widget=select_button,
152        )
153        self._column: bui.Widget | None = None
154
155        v -= 35
156
157        if uiscale is bui.UIScale.SMALL:
158            bui.containerwidget(
159                edit=self._root_widget, on_cancel_call=self.main_window_back
160            )
161        else:
162            bui.containerwidget(
163                edit=self._root_widget,
164                cancel_button=self._back_button,
165            )
166        bui.containerwidget(edit=self._root_widget, start_button=select_button)
167
168        self._selected_game_type: type[bs.GameActivity] | None = None
169
170        bui.containerwidget(
171            edit=self._root_widget, selected_child=self._scrollwidget
172        )
173
174        self._game_types: list[type[bs.GameActivity]] = []
175
176        # Get actual games loading in the bg.
177        bui.app.meta.load_exported_classes(
178            bs.GameActivity,
179            self._on_game_types_loaded,
180            completion_cb_in_bg_thread=True,
181        )
182
183        # Refresh with our initial empty list. We'll refresh again once
184        # game loading is complete.
185        self._refresh()
186
187    @override
188    def get_main_window_state(self) -> bui.MainWindowState:
189        # Support recreating our window for back/refresh purposes.
190        cls = type(self)
191
192        # Avoid dereferencing self from the lambda or we'll keep
193        # ourself alive indefinitely.
194        editcontroller = self._editcontroller
195
196        return bui.BasicMainWindowState(
197            create_call=lambda transition, origin_widget: cls(
198                transition=transition,
199                origin_widget=origin_widget,
200                editcontroller=editcontroller,
201            )
202        )
203
204    def _on_game_types_loaded(
205        self, gametypes: list[type[bs.GameActivity]]
206    ) -> None:
207        assert bui.app.classic is not None
208        store = bui.app.classic.store
209
210        # We asked for a bg thread completion cb so we can do some
211        # filtering here in the bg thread where its not gonna cause hitches.
212        assert not bui.in_logic_thread()
213        sessiontype = self._editcontroller.get_session_type()
214        unowned = store.get_unowned_game_types()
215        self._game_types = [
216            gt
217            for gt in gametypes
218            if gt not in unowned and gt.supports_session_type(sessiontype)
219        ]
220
221        # Sort in the current language.
222        self._game_types.sort(key=lambda g: g.get_display_string().evaluate())
223
224        # Tell ourself to refresh back in the logic thread.
225        bui.pushcall(self._refresh, from_other_thread=True)
226
227    def _refresh(self, select_get_more_games_button: bool = False) -> None:
228        if self._column is not None:
229            self._column.delete()
230
231        self._column = bui.columnwidget(
232            parent=self._scrollwidget, border=2, margin=0
233        )
234
235        for i, gametype in enumerate(self._game_types):
236
237            def _doit() -> None:
238                if self._select_button:
239                    bui.apptimer(0.1, self._select_button.activate)
240
241            txt = bui.textwidget(
242                parent=self._column,
243                position=(0, 0),
244                size=(self._scroll_width * 1.1, 24),
245                text=gametype.get_display_string(),
246                h_align='left',
247                v_align='center',
248                color=(0.8, 0.8, 0.8, 1.0),
249                maxwidth=self._scroll_width * 0.8,
250                on_select_call=bui.Call(self._set_selected_game_type, gametype),
251                always_highlight=True,
252                selectable=True,
253                on_activate_call=_doit,
254            )
255            if i == 0:
256                bui.widget(edit=txt, up_widget=self._back_button)
257
258        self._get_more_games_button = bui.buttonwidget(
259            parent=self._column,
260            autoselect=True,
261            label=bui.Lstr(resource=f'{self._r}.getMoreGamesText'),
262            color=(0.54, 0.52, 0.67),
263            textcolor=(0.7, 0.65, 0.7),
264            on_activate_call=self._on_get_more_games_press,
265            size=(178, 50),
266        )
267        if select_get_more_games_button:
268            bui.containerwidget(
269                edit=self._column,
270                selected_child=self._get_more_games_button,
271                visible_child=self._get_more_games_button,
272            )
273
274    def _on_get_more_games_press(self) -> None:
275        from bauiv1lib.account.signin import show_sign_in_prompt
276        from bauiv1lib.store.browser import StoreBrowserWindow
277
278        # No-op if we're not in control.
279        if not self.main_window_has_control():
280            return
281
282        plus = bui.app.plus
283        assert plus is not None
284
285        if plus.get_v1_account_state() != 'signed_in':
286            show_sign_in_prompt()
287            return
288
289        self.main_window_replace(
290            StoreBrowserWindow(
291                show_tab=StoreBrowserWindow.TabID.MINIGAMES,
292                origin_widget=self._get_more_games_button,
293                minimal_toolbars=True,
294            )
295        )
296
297    def _add(self) -> None:
298        bui.lock_all_input()  # Make sure no more commands happen.
299        bui.apptimer(0.1, bui.unlock_all_input)
300        assert self._selected_game_type is not None
301        self._editcontroller.add_game_type_selected(
302            self._selected_game_type, from_window=self
303        )
304
305    def _set_selected_game_type(self, gametype: type[bs.GameActivity]) -> None:
306        self._selected_game_type = gametype
307        bui.textwidget(
308            edit=self._selected_title_text, text=gametype.get_display_string()
309        )
310        bui.textwidget(
311            edit=self._selected_description_text,
312            text=gametype.get_description_display_string(
313                self._editcontroller.get_session_type()
314            ),
315        )
class PlaylistAddGameWindow(bauiv1._uitypes.MainWindow):
 17class PlaylistAddGameWindow(bui.MainWindow):
 18    """Window for selecting a game type to add to a playlist."""
 19
 20    def __init__(
 21        self,
 22        editcontroller: PlaylistEditController,
 23        transition: str | None = 'in_right',
 24        origin_widget: bui.Widget | None = None,
 25    ):
 26        self._editcontroller = editcontroller
 27        self._r = 'addGameWindow'
 28        assert bui.app.classic is not None
 29        uiscale = bui.app.ui_v1.uiscale
 30        self._width = 900 if uiscale is bui.UIScale.SMALL else 650
 31
 32        self._height = (
 33            1200.0
 34            if uiscale is bui.UIScale.SMALL
 35            else 450.0 if uiscale is bui.UIScale.MEDIUM else 500.0
 36        )
 37        self._scroll_width = 210
 38
 39        # Do some fancy math to fill all available screen area up to the
 40        # size of our backing container. This lets us fit to the exact
 41        # screen shape at small ui scale.
 42        screensize = bui.get_virtual_screen_size()
 43        scale = (
 44            2.4
 45            if uiscale is bui.UIScale.SMALL
 46            else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0
 47        )
 48        # Calc screen size in our local container space and clamp to a
 49        # bit smaller than our container size.
 50        target_width = min(self._width - 50, screensize[0] / scale)
 51        target_height = min(self._height - 70, screensize[1] / scale)
 52
 53        # To get top/left coords, go to the center of our window and
 54        # offset by half the width/height of our target area.
 55        yoffs = 0.5 * self._height + 0.5 * target_height + 5.0
 56        x_inset = 0.5 * self._width - 0.5 * target_width
 57
 58        super().__init__(
 59            root_widget=bui.containerwidget(
 60                size=(self._width, self._height),
 61                scale=scale,
 62                toolbar_visibility='menu_minimal',
 63            ),
 64            transition=transition,
 65            origin_widget=origin_widget,
 66            # We're affected by screen size only at small ui-scale.
 67            refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
 68        )
 69
 70        if uiscale is bui.UIScale.SMALL:
 71            self._back_button = bui.get_special_widget('back_button')
 72        else:
 73            self._back_button = bui.buttonwidget(
 74                parent=self._root_widget,
 75                position=(58 + x_inset, yoffs - 53),
 76                size=(60, 48),
 77                label=bui.charstr(bui.SpecialChar.BACK),
 78                autoselect=True,
 79                button_type='backSmall',
 80                on_activate_call=self.main_window_back,
 81            )
 82
 83        self._select_button = select_button = bui.buttonwidget(
 84            parent=self._root_widget,
 85            position=(
 86                x_inset + target_width - 172,
 87                yoffs - 50,
 88            ),
 89            autoselect=True,
 90            size=(160, 60),
 91            scale=0.75,
 92            text_scale=1.2,
 93            label=bui.Lstr(resource='selectText'),
 94            on_activate_call=self._add,
 95        )
 96
 97        bui.widget(
 98            edit=select_button,
 99            right_widget=bui.get_special_widget('squad_button'),
100        )
101
102        bui.textwidget(
103            parent=self._root_widget,
104            position=(self._width * 0.5, yoffs - 28),
105            size=(0, 0),
106            scale=1.0,
107            text=bui.Lstr(resource=f'{self._r}.titleText'),
108            h_align='center',
109            color=bui.app.ui_v1.title_color,
110            maxwidth=250,
111            v_align='center',
112        )
113        v = yoffs - 64
114
115        self._selected_title_text = bui.textwidget(
116            parent=self._root_widget,
117            position=(x_inset + self._scroll_width + 50 + 30, v - 15),
118            size=(0, 0),
119            scale=1.0,
120            color=(0.7, 1.0, 0.7, 1.0),
121            maxwidth=self._width - self._scroll_width - 150 - x_inset * 2,
122            h_align='left',
123            v_align='center',
124        )
125        v -= 30
126
127        self._selected_description_text = bui.textwidget(
128            parent=self._root_widget,
129            position=(x_inset + self._scroll_width + 50 + 30, v),
130            size=(0, 0),
131            scale=0.7,
132            color=(0.5, 0.8, 0.5, 1.0),
133            maxwidth=self._width - self._scroll_width - 150 - x_inset * 2,
134            h_align='left',
135        )
136
137        scroll_height = target_height - 60
138
139        v = yoffs - 60
140
141        self._scrollwidget = bui.scrollwidget(
142            parent=self._root_widget,
143            position=(x_inset + 61, v - scroll_height),
144            size=(self._scroll_width, scroll_height),
145            highlight=False,
146            border_opacity=0.4,
147        )
148        bui.widget(
149            edit=self._scrollwidget,
150            up_widget=self._back_button,
151            left_widget=self._back_button,
152            right_widget=select_button,
153        )
154        self._column: bui.Widget | None = None
155
156        v -= 35
157
158        if uiscale is bui.UIScale.SMALL:
159            bui.containerwidget(
160                edit=self._root_widget, on_cancel_call=self.main_window_back
161            )
162        else:
163            bui.containerwidget(
164                edit=self._root_widget,
165                cancel_button=self._back_button,
166            )
167        bui.containerwidget(edit=self._root_widget, start_button=select_button)
168
169        self._selected_game_type: type[bs.GameActivity] | None = None
170
171        bui.containerwidget(
172            edit=self._root_widget, selected_child=self._scrollwidget
173        )
174
175        self._game_types: list[type[bs.GameActivity]] = []
176
177        # Get actual games loading in the bg.
178        bui.app.meta.load_exported_classes(
179            bs.GameActivity,
180            self._on_game_types_loaded,
181            completion_cb_in_bg_thread=True,
182        )
183
184        # Refresh with our initial empty list. We'll refresh again once
185        # game loading is complete.
186        self._refresh()
187
188    @override
189    def get_main_window_state(self) -> bui.MainWindowState:
190        # Support recreating our window for back/refresh purposes.
191        cls = type(self)
192
193        # Avoid dereferencing self from the lambda or we'll keep
194        # ourself alive indefinitely.
195        editcontroller = self._editcontroller
196
197        return bui.BasicMainWindowState(
198            create_call=lambda transition, origin_widget: cls(
199                transition=transition,
200                origin_widget=origin_widget,
201                editcontroller=editcontroller,
202            )
203        )
204
205    def _on_game_types_loaded(
206        self, gametypes: list[type[bs.GameActivity]]
207    ) -> None:
208        assert bui.app.classic is not None
209        store = bui.app.classic.store
210
211        # We asked for a bg thread completion cb so we can do some
212        # filtering here in the bg thread where its not gonna cause hitches.
213        assert not bui.in_logic_thread()
214        sessiontype = self._editcontroller.get_session_type()
215        unowned = store.get_unowned_game_types()
216        self._game_types = [
217            gt
218            for gt in gametypes
219            if gt not in unowned and gt.supports_session_type(sessiontype)
220        ]
221
222        # Sort in the current language.
223        self._game_types.sort(key=lambda g: g.get_display_string().evaluate())
224
225        # Tell ourself to refresh back in the logic thread.
226        bui.pushcall(self._refresh, from_other_thread=True)
227
228    def _refresh(self, select_get_more_games_button: bool = False) -> None:
229        if self._column is not None:
230            self._column.delete()
231
232        self._column = bui.columnwidget(
233            parent=self._scrollwidget, border=2, margin=0
234        )
235
236        for i, gametype in enumerate(self._game_types):
237
238            def _doit() -> None:
239                if self._select_button:
240                    bui.apptimer(0.1, self._select_button.activate)
241
242            txt = bui.textwidget(
243                parent=self._column,
244                position=(0, 0),
245                size=(self._scroll_width * 1.1, 24),
246                text=gametype.get_display_string(),
247                h_align='left',
248                v_align='center',
249                color=(0.8, 0.8, 0.8, 1.0),
250                maxwidth=self._scroll_width * 0.8,
251                on_select_call=bui.Call(self._set_selected_game_type, gametype),
252                always_highlight=True,
253                selectable=True,
254                on_activate_call=_doit,
255            )
256            if i == 0:
257                bui.widget(edit=txt, up_widget=self._back_button)
258
259        self._get_more_games_button = bui.buttonwidget(
260            parent=self._column,
261            autoselect=True,
262            label=bui.Lstr(resource=f'{self._r}.getMoreGamesText'),
263            color=(0.54, 0.52, 0.67),
264            textcolor=(0.7, 0.65, 0.7),
265            on_activate_call=self._on_get_more_games_press,
266            size=(178, 50),
267        )
268        if select_get_more_games_button:
269            bui.containerwidget(
270                edit=self._column,
271                selected_child=self._get_more_games_button,
272                visible_child=self._get_more_games_button,
273            )
274
275    def _on_get_more_games_press(self) -> None:
276        from bauiv1lib.account.signin import show_sign_in_prompt
277        from bauiv1lib.store.browser import StoreBrowserWindow
278
279        # No-op if we're not in control.
280        if not self.main_window_has_control():
281            return
282
283        plus = bui.app.plus
284        assert plus is not None
285
286        if plus.get_v1_account_state() != 'signed_in':
287            show_sign_in_prompt()
288            return
289
290        self.main_window_replace(
291            StoreBrowserWindow(
292                show_tab=StoreBrowserWindow.TabID.MINIGAMES,
293                origin_widget=self._get_more_games_button,
294                minimal_toolbars=True,
295            )
296        )
297
298    def _add(self) -> None:
299        bui.lock_all_input()  # Make sure no more commands happen.
300        bui.apptimer(0.1, bui.unlock_all_input)
301        assert self._selected_game_type is not None
302        self._editcontroller.add_game_type_selected(
303            self._selected_game_type, from_window=self
304        )
305
306    def _set_selected_game_type(self, gametype: type[bs.GameActivity]) -> None:
307        self._selected_game_type = gametype
308        bui.textwidget(
309            edit=self._selected_title_text, text=gametype.get_display_string()
310        )
311        bui.textwidget(
312            edit=self._selected_description_text,
313            text=gametype.get_description_display_string(
314                self._editcontroller.get_session_type()
315            ),
316        )

Window for selecting a game type to add to a playlist.

PlaylistAddGameWindow( editcontroller: bauiv1lib.playlist.editcontroller.PlaylistEditController, transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
 20    def __init__(
 21        self,
 22        editcontroller: PlaylistEditController,
 23        transition: str | None = 'in_right',
 24        origin_widget: bui.Widget | None = None,
 25    ):
 26        self._editcontroller = editcontroller
 27        self._r = 'addGameWindow'
 28        assert bui.app.classic is not None
 29        uiscale = bui.app.ui_v1.uiscale
 30        self._width = 900 if uiscale is bui.UIScale.SMALL else 650
 31
 32        self._height = (
 33            1200.0
 34            if uiscale is bui.UIScale.SMALL
 35            else 450.0 if uiscale is bui.UIScale.MEDIUM else 500.0
 36        )
 37        self._scroll_width = 210
 38
 39        # Do some fancy math to fill all available screen area up to the
 40        # size of our backing container. This lets us fit to the exact
 41        # screen shape at small ui scale.
 42        screensize = bui.get_virtual_screen_size()
 43        scale = (
 44            2.4
 45            if uiscale is bui.UIScale.SMALL
 46            else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0
 47        )
 48        # Calc screen size in our local container space and clamp to a
 49        # bit smaller than our container size.
 50        target_width = min(self._width - 50, screensize[0] / scale)
 51        target_height = min(self._height - 70, screensize[1] / scale)
 52
 53        # To get top/left coords, go to the center of our window and
 54        # offset by half the width/height of our target area.
 55        yoffs = 0.5 * self._height + 0.5 * target_height + 5.0
 56        x_inset = 0.5 * self._width - 0.5 * target_width
 57
 58        super().__init__(
 59            root_widget=bui.containerwidget(
 60                size=(self._width, self._height),
 61                scale=scale,
 62                toolbar_visibility='menu_minimal',
 63            ),
 64            transition=transition,
 65            origin_widget=origin_widget,
 66            # We're affected by screen size only at small ui-scale.
 67            refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
 68        )
 69
 70        if uiscale is bui.UIScale.SMALL:
 71            self._back_button = bui.get_special_widget('back_button')
 72        else:
 73            self._back_button = bui.buttonwidget(
 74                parent=self._root_widget,
 75                position=(58 + x_inset, yoffs - 53),
 76                size=(60, 48),
 77                label=bui.charstr(bui.SpecialChar.BACK),
 78                autoselect=True,
 79                button_type='backSmall',
 80                on_activate_call=self.main_window_back,
 81            )
 82
 83        self._select_button = select_button = bui.buttonwidget(
 84            parent=self._root_widget,
 85            position=(
 86                x_inset + target_width - 172,
 87                yoffs - 50,
 88            ),
 89            autoselect=True,
 90            size=(160, 60),
 91            scale=0.75,
 92            text_scale=1.2,
 93            label=bui.Lstr(resource='selectText'),
 94            on_activate_call=self._add,
 95        )
 96
 97        bui.widget(
 98            edit=select_button,
 99            right_widget=bui.get_special_widget('squad_button'),
100        )
101
102        bui.textwidget(
103            parent=self._root_widget,
104            position=(self._width * 0.5, yoffs - 28),
105            size=(0, 0),
106            scale=1.0,
107            text=bui.Lstr(resource=f'{self._r}.titleText'),
108            h_align='center',
109            color=bui.app.ui_v1.title_color,
110            maxwidth=250,
111            v_align='center',
112        )
113        v = yoffs - 64
114
115        self._selected_title_text = bui.textwidget(
116            parent=self._root_widget,
117            position=(x_inset + self._scroll_width + 50 + 30, v - 15),
118            size=(0, 0),
119            scale=1.0,
120            color=(0.7, 1.0, 0.7, 1.0),
121            maxwidth=self._width - self._scroll_width - 150 - x_inset * 2,
122            h_align='left',
123            v_align='center',
124        )
125        v -= 30
126
127        self._selected_description_text = bui.textwidget(
128            parent=self._root_widget,
129            position=(x_inset + self._scroll_width + 50 + 30, v),
130            size=(0, 0),
131            scale=0.7,
132            color=(0.5, 0.8, 0.5, 1.0),
133            maxwidth=self._width - self._scroll_width - 150 - x_inset * 2,
134            h_align='left',
135        )
136
137        scroll_height = target_height - 60
138
139        v = yoffs - 60
140
141        self._scrollwidget = bui.scrollwidget(
142            parent=self._root_widget,
143            position=(x_inset + 61, v - scroll_height),
144            size=(self._scroll_width, scroll_height),
145            highlight=False,
146            border_opacity=0.4,
147        )
148        bui.widget(
149            edit=self._scrollwidget,
150            up_widget=self._back_button,
151            left_widget=self._back_button,
152            right_widget=select_button,
153        )
154        self._column: bui.Widget | None = None
155
156        v -= 35
157
158        if uiscale is bui.UIScale.SMALL:
159            bui.containerwidget(
160                edit=self._root_widget, on_cancel_call=self.main_window_back
161            )
162        else:
163            bui.containerwidget(
164                edit=self._root_widget,
165                cancel_button=self._back_button,
166            )
167        bui.containerwidget(edit=self._root_widget, start_button=select_button)
168
169        self._selected_game_type: type[bs.GameActivity] | None = None
170
171        bui.containerwidget(
172            edit=self._root_widget, selected_child=self._scrollwidget
173        )
174
175        self._game_types: list[type[bs.GameActivity]] = []
176
177        # Get actual games loading in the bg.
178        bui.app.meta.load_exported_classes(
179            bs.GameActivity,
180            self._on_game_types_loaded,
181            completion_cb_in_bg_thread=True,
182        )
183
184        # Refresh with our initial empty list. We'll refresh again once
185        # game loading is complete.
186        self._refresh()

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:
188    @override
189    def get_main_window_state(self) -> bui.MainWindowState:
190        # Support recreating our window for back/refresh purposes.
191        cls = type(self)
192
193        # Avoid dereferencing self from the lambda or we'll keep
194        # ourself alive indefinitely.
195        editcontroller = self._editcontroller
196
197        return bui.BasicMainWindowState(
198            create_call=lambda transition, origin_widget: cls(
199                transition=transition,
200                origin_widget=origin_widget,
201                editcontroller=editcontroller,
202            )
203        )

Return a WindowState to recreate this window, if supported.