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