bauiv1lib.playlist.mapselect

Provides UI for selecting maps in playlists.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides UI for selecting maps in playlists."""
  4
  5from __future__ import annotations
  6
  7import math
  8from typing import TYPE_CHECKING
  9
 10import bauiv1 as bui
 11
 12if TYPE_CHECKING:
 13    from typing import Any, Callable
 14
 15    import bascenev1 as bs
 16
 17
 18class PlaylistMapSelectWindow(bui.Window):
 19    """Window to select a map."""
 20
 21    def __init__(
 22        self,
 23        gametype: type[bs.GameActivity],
 24        sessiontype: type[bs.Session],
 25        config: dict[str, Any],
 26        edit_info: dict[str, Any],
 27        completion_call: Callable[[dict[str, Any] | None], Any],
 28        transition: str = 'in_right',
 29    ):
 30        from bascenev1 import get_filtered_map_name
 31
 32        self._gametype = gametype
 33        self._sessiontype = sessiontype
 34        self._config = config
 35        self._completion_call = completion_call
 36        self._edit_info = edit_info
 37        self._maps: list[tuple[str, bui.Texture]] = []
 38        try:
 39            self._previous_map = get_filtered_map_name(
 40                config['settings']['map']
 41            )
 42        except Exception:
 43            self._previous_map = ''
 44
 45        assert bui.app.classic is not None
 46        uiscale = bui.app.ui_v1.uiscale
 47        width = 815 if uiscale is bui.UIScale.SMALL else 615
 48        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 49        height = (
 50            400
 51            if uiscale is bui.UIScale.SMALL
 52            else 480 if uiscale is bui.UIScale.MEDIUM else 600
 53        )
 54
 55        top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
 56        super().__init__(
 57            root_widget=bui.containerwidget(
 58                size=(width, height + top_extra),
 59                transition=transition,
 60                scale=(
 61                    2.17
 62                    if uiscale is bui.UIScale.SMALL
 63                    else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
 64                ),
 65                stack_offset=(
 66                    (0, -27) if uiscale is bui.UIScale.SMALL else (0, 0)
 67                ),
 68            )
 69        )
 70
 71        self._cancel_button = btn = bui.buttonwidget(
 72            parent=self._root_widget,
 73            position=(38 + x_inset, height - 67),
 74            size=(140, 50),
 75            scale=0.9,
 76            text_scale=1.0,
 77            autoselect=True,
 78            label=bui.Lstr(resource='cancelText'),
 79            on_activate_call=self._cancel,
 80        )
 81
 82        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
 83        bui.textwidget(
 84            parent=self._root_widget,
 85            position=(width * 0.5, height - 46),
 86            size=(0, 0),
 87            maxwidth=260,
 88            scale=1.1,
 89            text=bui.Lstr(
 90                resource='mapSelectTitleText',
 91                subs=[('${GAME}', self._gametype.get_display_string())],
 92            ),
 93            color=bui.app.ui_v1.title_color,
 94            h_align='center',
 95            v_align='center',
 96        )
 97        v = height - 70
 98        self._scroll_width = width - (80 + 2 * x_inset)
 99        self._scroll_height = height - 140
100
101        self._scrollwidget = bui.scrollwidget(
102            parent=self._root_widget,
103            position=(40 + x_inset, v - self._scroll_height),
104            size=(self._scroll_width, self._scroll_height),
105        )
106        bui.containerwidget(
107            edit=self._root_widget, selected_child=self._scrollwidget
108        )
109        bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
110
111        self._subcontainer: bui.Widget | None = None
112        self._refresh()
113
114    def _refresh(self, select_get_more_maps_button: bool = False) -> None:
115        # pylint: disable=too-many-statements
116        # pylint: disable=too-many-branches
117        # pylint: disable=too-many-locals
118        from bascenev1 import (
119            get_map_class,
120            get_map_display_string,
121        )
122
123        assert bui.app.classic is not None
124        store = bui.app.classic.store
125        # Kill old.
126        if self._subcontainer is not None:
127            self._subcontainer.delete()
128
129        mesh_opaque = bui.getmesh('level_select_button_opaque')
130        mesh_transparent = bui.getmesh('level_select_button_transparent')
131
132        self._maps = []
133        map_list = self._gametype.get_supported_maps(self._sessiontype)
134        map_list_sorted = list(map_list)
135        map_list_sorted.sort()
136        unowned_maps = store.get_unowned_maps()
137
138        for mapname in map_list_sorted:
139            # Disallow ones we don't own.
140            if mapname in unowned_maps:
141                continue
142            map_tex_name = get_map_class(mapname).get_preview_texture_name()
143            if map_tex_name is not None:
144                try:
145                    map_tex = bui.gettexture(map_tex_name)
146                    self._maps.append((mapname, map_tex))
147                except Exception:
148                    print(f'Invalid map preview texture: "{map_tex_name}".')
149            else:
150                print('Error: no map preview texture for map:', mapname)
151
152        count = len(self._maps)
153        columns = 2
154        rows = int(math.ceil(float(count) / columns))
155        button_width = 220
156        button_height = button_width * 0.5
157        button_buffer_h = 16
158        button_buffer_v = 19
159        self._sub_width = self._scroll_width * 0.95
160        self._sub_height = (
161            5 + rows * (button_height + 2 * button_buffer_v) + 100
162        )
163        self._subcontainer = bui.containerwidget(
164            parent=self._scrollwidget,
165            size=(self._sub_width, self._sub_height),
166            background=False,
167        )
168        index = 0
169        mask_texture = bui.gettexture('mapPreviewMask')
170        h_offs = 130 if len(self._maps) == 1 else 0
171        for y in range(rows):
172            for x in range(columns):
173                pos = (
174                    x * (button_width + 2 * button_buffer_h)
175                    + button_buffer_h
176                    + h_offs,
177                    self._sub_height
178                    - (y + 1) * (button_height + 2 * button_buffer_v)
179                    + 12,
180                )
181                btn = bui.buttonwidget(
182                    parent=self._subcontainer,
183                    button_type='square',
184                    size=(button_width, button_height),
185                    autoselect=True,
186                    texture=self._maps[index][1],
187                    mask_texture=mask_texture,
188                    mesh_opaque=mesh_opaque,
189                    mesh_transparent=mesh_transparent,
190                    label='',
191                    color=(1, 1, 1),
192                    on_activate_call=bui.Call(
193                        self._select_with_delay, self._maps[index][0]
194                    ),
195                    position=pos,
196                )
197                if x == 0:
198                    bui.widget(edit=btn, left_widget=self._cancel_button)
199                if y == 0:
200                    bui.widget(edit=btn, up_widget=self._cancel_button)
201                if x == columns - 1 and bui.app.ui_v1.use_toolbars:
202                    bui.widget(
203                        edit=btn,
204                        right_widget=bui.get_special_widget('party_button'),
205                    )
206
207                bui.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
208                if self._maps[index][0] == self._previous_map:
209                    bui.containerwidget(
210                        edit=self._subcontainer,
211                        selected_child=btn,
212                        visible_child=btn,
213                    )
214                name = get_map_display_string(self._maps[index][0])
215                bui.textwidget(
216                    parent=self._subcontainer,
217                    text=name,
218                    position=(pos[0] + button_width * 0.5, pos[1] - 12),
219                    size=(0, 0),
220                    scale=0.5,
221                    maxwidth=button_width,
222                    draw_controller=btn,
223                    h_align='center',
224                    v_align='center',
225                    color=(0.8, 0.8, 0.8, 0.8),
226                )
227                index += 1
228
229                if index >= count:
230                    break
231            if index >= count:
232                break
233        self._get_more_maps_button = btn = bui.buttonwidget(
234            parent=self._subcontainer,
235            size=(self._sub_width * 0.8, 60),
236            position=(self._sub_width * 0.1, 30),
237            label=bui.Lstr(resource='mapSelectGetMoreMapsText'),
238            on_activate_call=self._on_store_press,
239            color=(0.6, 0.53, 0.63),
240            textcolor=(0.75, 0.7, 0.8),
241            autoselect=True,
242        )
243        bui.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30)
244        if select_get_more_maps_button:
245            bui.containerwidget(
246                edit=self._subcontainer, selected_child=btn, visible_child=btn
247            )
248
249    def _on_store_press(self) -> None:
250        from bauiv1lib import account
251        from bauiv1lib.store.browser import StoreBrowserWindow
252
253        plus = bui.app.plus
254        assert plus is not None
255
256        if plus.get_v1_account_state() != 'signed_in':
257            account.show_sign_in_prompt()
258            return
259        StoreBrowserWindow(
260            modal=True,
261            show_tab=StoreBrowserWindow.TabID.MAPS,
262            on_close_call=self._on_store_close,
263            origin_widget=self._get_more_maps_button,
264        )
265
266    def _on_store_close(self) -> None:
267        self._refresh(select_get_more_maps_button=True)
268
269    def _select(self, map_name: str) -> None:
270        from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
271
272        # no-op if our underlying widget is dead or on its way out.
273        if not self._root_widget or self._root_widget.transitioning_out:
274            return
275
276        self._config['settings']['map'] = map_name
277        bui.containerwidget(edit=self._root_widget, transition='out_right')
278        assert bui.app.classic is not None
279        bui.app.ui_v1.set_main_menu_window(
280            PlaylistEditGameWindow(
281                self._gametype,
282                self._sessiontype,
283                self._config,
284                self._completion_call,
285                default_selection='map',
286                transition='in_left',
287                edit_info=self._edit_info,
288            ).get_root_widget(),
289            from_window=self._root_widget,
290        )
291
292    def _select_with_delay(self, map_name: str) -> None:
293        bui.lock_all_input()
294        bui.apptimer(0.1, bui.unlock_all_input)
295        bui.apptimer(0.1, bui.WeakCall(self._select, map_name))
296
297    def _cancel(self) -> None:
298        from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
299
300        # no-op if our underlying widget is dead or on its way out.
301        if not self._root_widget or self._root_widget.transitioning_out:
302            return
303
304        bui.containerwidget(edit=self._root_widget, transition='out_right')
305        assert bui.app.classic is not None
306        bui.app.ui_v1.set_main_menu_window(
307            PlaylistEditGameWindow(
308                self._gametype,
309                self._sessiontype,
310                self._config,
311                self._completion_call,
312                default_selection='map',
313                transition='in_left',
314                edit_info=self._edit_info,
315            ).get_root_widget(),
316            from_window=self._root_widget,
317        )
class PlaylistMapSelectWindow(bauiv1._uitypes.Window):
 19class PlaylistMapSelectWindow(bui.Window):
 20    """Window to select a map."""
 21
 22    def __init__(
 23        self,
 24        gametype: type[bs.GameActivity],
 25        sessiontype: type[bs.Session],
 26        config: dict[str, Any],
 27        edit_info: dict[str, Any],
 28        completion_call: Callable[[dict[str, Any] | None], Any],
 29        transition: str = 'in_right',
 30    ):
 31        from bascenev1 import get_filtered_map_name
 32
 33        self._gametype = gametype
 34        self._sessiontype = sessiontype
 35        self._config = config
 36        self._completion_call = completion_call
 37        self._edit_info = edit_info
 38        self._maps: list[tuple[str, bui.Texture]] = []
 39        try:
 40            self._previous_map = get_filtered_map_name(
 41                config['settings']['map']
 42            )
 43        except Exception:
 44            self._previous_map = ''
 45
 46        assert bui.app.classic is not None
 47        uiscale = bui.app.ui_v1.uiscale
 48        width = 815 if uiscale is bui.UIScale.SMALL else 615
 49        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 50        height = (
 51            400
 52            if uiscale is bui.UIScale.SMALL
 53            else 480 if uiscale is bui.UIScale.MEDIUM else 600
 54        )
 55
 56        top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
 57        super().__init__(
 58            root_widget=bui.containerwidget(
 59                size=(width, height + top_extra),
 60                transition=transition,
 61                scale=(
 62                    2.17
 63                    if uiscale is bui.UIScale.SMALL
 64                    else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
 65                ),
 66                stack_offset=(
 67                    (0, -27) if uiscale is bui.UIScale.SMALL else (0, 0)
 68                ),
 69            )
 70        )
 71
 72        self._cancel_button = btn = bui.buttonwidget(
 73            parent=self._root_widget,
 74            position=(38 + x_inset, height - 67),
 75            size=(140, 50),
 76            scale=0.9,
 77            text_scale=1.0,
 78            autoselect=True,
 79            label=bui.Lstr(resource='cancelText'),
 80            on_activate_call=self._cancel,
 81        )
 82
 83        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
 84        bui.textwidget(
 85            parent=self._root_widget,
 86            position=(width * 0.5, height - 46),
 87            size=(0, 0),
 88            maxwidth=260,
 89            scale=1.1,
 90            text=bui.Lstr(
 91                resource='mapSelectTitleText',
 92                subs=[('${GAME}', self._gametype.get_display_string())],
 93            ),
 94            color=bui.app.ui_v1.title_color,
 95            h_align='center',
 96            v_align='center',
 97        )
 98        v = height - 70
 99        self._scroll_width = width - (80 + 2 * x_inset)
100        self._scroll_height = height - 140
101
102        self._scrollwidget = bui.scrollwidget(
103            parent=self._root_widget,
104            position=(40 + x_inset, v - self._scroll_height),
105            size=(self._scroll_width, self._scroll_height),
106        )
107        bui.containerwidget(
108            edit=self._root_widget, selected_child=self._scrollwidget
109        )
110        bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
111
112        self._subcontainer: bui.Widget | None = None
113        self._refresh()
114
115    def _refresh(self, select_get_more_maps_button: bool = False) -> None:
116        # pylint: disable=too-many-statements
117        # pylint: disable=too-many-branches
118        # pylint: disable=too-many-locals
119        from bascenev1 import (
120            get_map_class,
121            get_map_display_string,
122        )
123
124        assert bui.app.classic is not None
125        store = bui.app.classic.store
126        # Kill old.
127        if self._subcontainer is not None:
128            self._subcontainer.delete()
129
130        mesh_opaque = bui.getmesh('level_select_button_opaque')
131        mesh_transparent = bui.getmesh('level_select_button_transparent')
132
133        self._maps = []
134        map_list = self._gametype.get_supported_maps(self._sessiontype)
135        map_list_sorted = list(map_list)
136        map_list_sorted.sort()
137        unowned_maps = store.get_unowned_maps()
138
139        for mapname in map_list_sorted:
140            # Disallow ones we don't own.
141            if mapname in unowned_maps:
142                continue
143            map_tex_name = get_map_class(mapname).get_preview_texture_name()
144            if map_tex_name is not None:
145                try:
146                    map_tex = bui.gettexture(map_tex_name)
147                    self._maps.append((mapname, map_tex))
148                except Exception:
149                    print(f'Invalid map preview texture: "{map_tex_name}".')
150            else:
151                print('Error: no map preview texture for map:', mapname)
152
153        count = len(self._maps)
154        columns = 2
155        rows = int(math.ceil(float(count) / columns))
156        button_width = 220
157        button_height = button_width * 0.5
158        button_buffer_h = 16
159        button_buffer_v = 19
160        self._sub_width = self._scroll_width * 0.95
161        self._sub_height = (
162            5 + rows * (button_height + 2 * button_buffer_v) + 100
163        )
164        self._subcontainer = bui.containerwidget(
165            parent=self._scrollwidget,
166            size=(self._sub_width, self._sub_height),
167            background=False,
168        )
169        index = 0
170        mask_texture = bui.gettexture('mapPreviewMask')
171        h_offs = 130 if len(self._maps) == 1 else 0
172        for y in range(rows):
173            for x in range(columns):
174                pos = (
175                    x * (button_width + 2 * button_buffer_h)
176                    + button_buffer_h
177                    + h_offs,
178                    self._sub_height
179                    - (y + 1) * (button_height + 2 * button_buffer_v)
180                    + 12,
181                )
182                btn = bui.buttonwidget(
183                    parent=self._subcontainer,
184                    button_type='square',
185                    size=(button_width, button_height),
186                    autoselect=True,
187                    texture=self._maps[index][1],
188                    mask_texture=mask_texture,
189                    mesh_opaque=mesh_opaque,
190                    mesh_transparent=mesh_transparent,
191                    label='',
192                    color=(1, 1, 1),
193                    on_activate_call=bui.Call(
194                        self._select_with_delay, self._maps[index][0]
195                    ),
196                    position=pos,
197                )
198                if x == 0:
199                    bui.widget(edit=btn, left_widget=self._cancel_button)
200                if y == 0:
201                    bui.widget(edit=btn, up_widget=self._cancel_button)
202                if x == columns - 1 and bui.app.ui_v1.use_toolbars:
203                    bui.widget(
204                        edit=btn,
205                        right_widget=bui.get_special_widget('party_button'),
206                    )
207
208                bui.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
209                if self._maps[index][0] == self._previous_map:
210                    bui.containerwidget(
211                        edit=self._subcontainer,
212                        selected_child=btn,
213                        visible_child=btn,
214                    )
215                name = get_map_display_string(self._maps[index][0])
216                bui.textwidget(
217                    parent=self._subcontainer,
218                    text=name,
219                    position=(pos[0] + button_width * 0.5, pos[1] - 12),
220                    size=(0, 0),
221                    scale=0.5,
222                    maxwidth=button_width,
223                    draw_controller=btn,
224                    h_align='center',
225                    v_align='center',
226                    color=(0.8, 0.8, 0.8, 0.8),
227                )
228                index += 1
229
230                if index >= count:
231                    break
232            if index >= count:
233                break
234        self._get_more_maps_button = btn = bui.buttonwidget(
235            parent=self._subcontainer,
236            size=(self._sub_width * 0.8, 60),
237            position=(self._sub_width * 0.1, 30),
238            label=bui.Lstr(resource='mapSelectGetMoreMapsText'),
239            on_activate_call=self._on_store_press,
240            color=(0.6, 0.53, 0.63),
241            textcolor=(0.75, 0.7, 0.8),
242            autoselect=True,
243        )
244        bui.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30)
245        if select_get_more_maps_button:
246            bui.containerwidget(
247                edit=self._subcontainer, selected_child=btn, visible_child=btn
248            )
249
250    def _on_store_press(self) -> None:
251        from bauiv1lib import account
252        from bauiv1lib.store.browser import StoreBrowserWindow
253
254        plus = bui.app.plus
255        assert plus is not None
256
257        if plus.get_v1_account_state() != 'signed_in':
258            account.show_sign_in_prompt()
259            return
260        StoreBrowserWindow(
261            modal=True,
262            show_tab=StoreBrowserWindow.TabID.MAPS,
263            on_close_call=self._on_store_close,
264            origin_widget=self._get_more_maps_button,
265        )
266
267    def _on_store_close(self) -> None:
268        self._refresh(select_get_more_maps_button=True)
269
270    def _select(self, map_name: str) -> None:
271        from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
272
273        # no-op if our underlying widget is dead or on its way out.
274        if not self._root_widget or self._root_widget.transitioning_out:
275            return
276
277        self._config['settings']['map'] = map_name
278        bui.containerwidget(edit=self._root_widget, transition='out_right')
279        assert bui.app.classic is not None
280        bui.app.ui_v1.set_main_menu_window(
281            PlaylistEditGameWindow(
282                self._gametype,
283                self._sessiontype,
284                self._config,
285                self._completion_call,
286                default_selection='map',
287                transition='in_left',
288                edit_info=self._edit_info,
289            ).get_root_widget(),
290            from_window=self._root_widget,
291        )
292
293    def _select_with_delay(self, map_name: str) -> None:
294        bui.lock_all_input()
295        bui.apptimer(0.1, bui.unlock_all_input)
296        bui.apptimer(0.1, bui.WeakCall(self._select, map_name))
297
298    def _cancel(self) -> None:
299        from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
300
301        # no-op if our underlying widget is dead or on its way out.
302        if not self._root_widget or self._root_widget.transitioning_out:
303            return
304
305        bui.containerwidget(edit=self._root_widget, transition='out_right')
306        assert bui.app.classic is not None
307        bui.app.ui_v1.set_main_menu_window(
308            PlaylistEditGameWindow(
309                self._gametype,
310                self._sessiontype,
311                self._config,
312                self._completion_call,
313                default_selection='map',
314                transition='in_left',
315                edit_info=self._edit_info,
316            ).get_root_widget(),
317            from_window=self._root_widget,
318        )

Window to select a map.

PlaylistMapSelectWindow( gametype: type[bascenev1._gameactivity.GameActivity], sessiontype: type[bascenev1._session.Session], config: dict[str, typing.Any], edit_info: dict[str, typing.Any], completion_call: Callable[[dict[str, Any] | None], Any], transition: str = 'in_right')
 22    def __init__(
 23        self,
 24        gametype: type[bs.GameActivity],
 25        sessiontype: type[bs.Session],
 26        config: dict[str, Any],
 27        edit_info: dict[str, Any],
 28        completion_call: Callable[[dict[str, Any] | None], Any],
 29        transition: str = 'in_right',
 30    ):
 31        from bascenev1 import get_filtered_map_name
 32
 33        self._gametype = gametype
 34        self._sessiontype = sessiontype
 35        self._config = config
 36        self._completion_call = completion_call
 37        self._edit_info = edit_info
 38        self._maps: list[tuple[str, bui.Texture]] = []
 39        try:
 40            self._previous_map = get_filtered_map_name(
 41                config['settings']['map']
 42            )
 43        except Exception:
 44            self._previous_map = ''
 45
 46        assert bui.app.classic is not None
 47        uiscale = bui.app.ui_v1.uiscale
 48        width = 815 if uiscale is bui.UIScale.SMALL else 615
 49        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 50        height = (
 51            400
 52            if uiscale is bui.UIScale.SMALL
 53            else 480 if uiscale is bui.UIScale.MEDIUM else 600
 54        )
 55
 56        top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
 57        super().__init__(
 58            root_widget=bui.containerwidget(
 59                size=(width, height + top_extra),
 60                transition=transition,
 61                scale=(
 62                    2.17
 63                    if uiscale is bui.UIScale.SMALL
 64                    else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
 65                ),
 66                stack_offset=(
 67                    (0, -27) if uiscale is bui.UIScale.SMALL else (0, 0)
 68                ),
 69            )
 70        )
 71
 72        self._cancel_button = btn = bui.buttonwidget(
 73            parent=self._root_widget,
 74            position=(38 + x_inset, height - 67),
 75            size=(140, 50),
 76            scale=0.9,
 77            text_scale=1.0,
 78            autoselect=True,
 79            label=bui.Lstr(resource='cancelText'),
 80            on_activate_call=self._cancel,
 81        )
 82
 83        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
 84        bui.textwidget(
 85            parent=self._root_widget,
 86            position=(width * 0.5, height - 46),
 87            size=(0, 0),
 88            maxwidth=260,
 89            scale=1.1,
 90            text=bui.Lstr(
 91                resource='mapSelectTitleText',
 92                subs=[('${GAME}', self._gametype.get_display_string())],
 93            ),
 94            color=bui.app.ui_v1.title_color,
 95            h_align='center',
 96            v_align='center',
 97        )
 98        v = height - 70
 99        self._scroll_width = width - (80 + 2 * x_inset)
100        self._scroll_height = height - 140
101
102        self._scrollwidget = bui.scrollwidget(
103            parent=self._root_widget,
104            position=(40 + x_inset, v - self._scroll_height),
105            size=(self._scroll_width, self._scroll_height),
106        )
107        bui.containerwidget(
108            edit=self._root_widget, selected_child=self._scrollwidget
109        )
110        bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
111
112        self._subcontainer: bui.Widget | None = None
113        self._refresh()
Inherited Members
bauiv1._uitypes.Window
get_root_widget