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

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 = 715 if uiscale is bui.UIScale.SMALL else 615
 49        x_inset = 50 if uiscale is bui.UIScale.SMALL else 0
 50        height = (
 51            400
 52            if uiscale is bui.UIScale.SMALL
 53            else 480
 54            if uiscale is bui.UIScale.MEDIUM
 55            else 600
 56        )
 57
 58        top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
 59        super().__init__(
 60            root_widget=bui.containerwidget(
 61                size=(width, height + top_extra),
 62                transition=transition,
 63                scale=(
 64                    2.17
 65                    if uiscale is bui.UIScale.SMALL
 66                    else 1.3
 67                    if uiscale is bui.UIScale.MEDIUM
 68                    else 1.0
 69                ),
 70                stack_offset=(0, -27)
 71                if uiscale is bui.UIScale.SMALL
 72                else (0, 0),
 73            )
 74        )
 75
 76        self._cancel_button = btn = bui.buttonwidget(
 77            parent=self._root_widget,
 78            position=(38 + x_inset, height - 67),
 79            size=(140, 50),
 80            scale=0.9,
 81            text_scale=1.0,
 82            autoselect=True,
 83            label=bui.Lstr(resource='cancelText'),
 84            on_activate_call=self._cancel,
 85        )
 86
 87        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
 88        bui.textwidget(
 89            parent=self._root_widget,
 90            position=(width * 0.5, height - 46),
 91            size=(0, 0),
 92            maxwidth=260,
 93            scale=1.1,
 94            text=bui.Lstr(
 95                resource='mapSelectTitleText',
 96                subs=[('${GAME}', self._gametype.get_display_string())],
 97            ),
 98            color=bui.app.ui_v1.title_color,
 99            h_align='center',
100            v_align='center',
101        )
102        v = height - 70
103        self._scroll_width = width - (80 + 2 * x_inset)
104        self._scroll_height = height - 140
105
106        self._scrollwidget = bui.scrollwidget(
107            parent=self._root_widget,
108            position=(40 + x_inset, v - self._scroll_height),
109            size=(self._scroll_width, self._scroll_height),
110        )
111        bui.containerwidget(
112            edit=self._root_widget, selected_child=self._scrollwidget
113        )
114        bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
115
116        self._subcontainer: bui.Widget | None = None
117        self._refresh()
Inherited Members
bauiv1._uitypes.Window
get_root_widget