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