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

Window to select a map.

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