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

Window to select a map.

PlaylistMapSelectWindow( gametype: type[bascenev1.GameActivity], sessiontype: type[bascenev1.Session], config: dict[str, typing.Any], edit_info: dict[str, typing.Any], completion_call: Callable[[dict[str, Any] | None, bauiv1.MainWindow], Any], transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None, select_get_more_maps_button: bool = False)
 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, bui.MainWindow], Any],
 29        transition: str | None = 'in_right',
 30        origin_widget: bui.Widget | None = None,
 31        select_get_more_maps_button: bool = False,
 32    ):
 33        # pylint: disable=too-many-locals
 34        # pylint: disable=too-many-positional-arguments
 35
 36        from bascenev1 import get_filtered_map_name
 37
 38        self._gametype = gametype
 39        self._sessiontype = sessiontype
 40        self._config = config
 41        self._completion_call = completion_call
 42        self._edit_info = edit_info
 43        self._maps: list[tuple[str, bui.Texture]] = []
 44        self._selected_get_more_maps = False
 45        try:
 46            self._previous_map = get_filtered_map_name(
 47                config['settings']['map']
 48            )
 49        except Exception:
 50            self._previous_map = ''
 51
 52        assert bui.app.classic is not None
 53        uiscale = bui.app.ui_v1.uiscale
 54        width = 815 if uiscale is bui.UIScale.SMALL else 615
 55        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 56        height = (
 57            420
 58            if uiscale is bui.UIScale.SMALL
 59            else 480 if uiscale is bui.UIScale.MEDIUM else 600
 60        )
 61        yoffs = -37 if uiscale is bui.UIScale.SMALL else 0
 62
 63        super().__init__(
 64            root_widget=bui.containerwidget(
 65                size=(width, height),
 66                scale=(
 67                    1.95
 68                    if uiscale is bui.UIScale.SMALL
 69                    else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
 70                ),
 71                stack_offset=(
 72                    (0, 0) if uiscale is bui.UIScale.SMALL else (0, 0)
 73                ),
 74            ),
 75            transition=transition,
 76            origin_widget=origin_widget,
 77        )
 78
 79        self._cancel_button = btn = bui.buttonwidget(
 80            parent=self._root_widget,
 81            position=(38 + x_inset, height - 67 + yoffs),
 82            size=(140, 50),
 83            scale=0.9,
 84            text_scale=1.0,
 85            autoselect=True,
 86            label=bui.Lstr(resource='cancelText'),
 87            on_activate_call=self.main_window_back,
 88        )
 89
 90        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
 91        bui.textwidget(
 92            parent=self._root_widget,
 93            position=(width * 0.5, height - 46 + yoffs),
 94            size=(0, 0),
 95            maxwidth=260,
 96            scale=1.1,
 97            text=bui.Lstr(
 98                resource='mapSelectTitleText',
 99                subs=[('${GAME}', self._gametype.get_display_string())],
100            ),
101            color=bui.app.ui_v1.title_color,
102            h_align='center',
103            v_align='center',
104        )
105        v = height - 70 + yoffs
106        self._scroll_width = width - (80 + 2 * x_inset)
107        self._scroll_height = height - (
108            170 if uiscale is bui.UIScale.SMALL else 140
109        )
110
111        self._scrollwidget = bui.scrollwidget(
112            parent=self._root_widget,
113            position=(40 + x_inset, v - self._scroll_height),
114            size=(self._scroll_width, self._scroll_height),
115            border_opacity=0.4,
116        )
117        bui.containerwidget(
118            edit=self._root_widget, selected_child=self._scrollwidget
119        )
120        bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
121
122        self._subcontainer: bui.Widget | None = None
123        self._refresh(select_get_more_maps_button=select_get_more_maps_button)

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:
125    @override
126    def get_main_window_state(self) -> bui.MainWindowState:
127        # Support recreating our window for back/refresh purposes.
128        cls = type(self)
129
130        # Pull things out of self here; if we do it in the lambda we'll
131        # keep ourself alive.
132        gametype = self._gametype
133        sessiontype = self._sessiontype
134        config = self._config
135        edit_info = self._edit_info
136        completion_call = self._completion_call
137        select_get_more_maps = self._selected_get_more_maps
138
139        return bui.BasicMainWindowState(
140            create_call=lambda transition, origin_widget: cls(
141                transition=transition,
142                origin_widget=origin_widget,
143                gametype=gametype,
144                sessiontype=sessiontype,
145                config=config,
146                edit_info=edit_info,
147                completion_call=completion_call,
148                select_get_more_maps_button=select_get_more_maps,
149            )
150        )

Return a WindowState to recreate this window, if supported.