bauiv1lib.profile.browser
UI functionality related to browsing player profiles.
1# Released under the MIT License. See LICENSE for details. 2# 3"""UI functionality related to browsing player profiles.""" 4 5from __future__ import annotations 6 7import logging 8from typing import TYPE_CHECKING 9 10import bauiv1 as bui 11import bascenev1 as bs 12 13if TYPE_CHECKING: 14 from typing import Any 15 16 17class ProfileBrowserWindow(bui.Window): 18 """Window for browsing player profiles.""" 19 20 def __init__( 21 self, 22 transition: str = 'in_right', 23 in_main_menu: bool = True, 24 selected_profile: str | None = None, 25 origin_widget: bui.Widget | None = None, 26 ): 27 # pylint: disable=too-many-statements 28 # pylint: disable=too-many-locals 29 self._in_main_menu = in_main_menu 30 if self._in_main_menu: 31 back_label = bui.Lstr(resource='backText') 32 else: 33 back_label = bui.Lstr(resource='doneText') 34 assert bui.app.classic is not None 35 uiscale = bui.app.ui_v1.uiscale 36 self._width = 800.0 if uiscale is bui.UIScale.SMALL else 600.0 37 x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0 38 self._height = ( 39 360.0 40 if uiscale is bui.UIScale.SMALL 41 else 385.0 if uiscale is bui.UIScale.MEDIUM else 410.0 42 ) 43 44 # If we're being called up standalone, handle pause/resume ourself. 45 if not self._in_main_menu: 46 assert bui.app.classic is not None 47 bui.app.classic.pause() 48 49 # If they provided an origin-widget, scale up from that. 50 scale_origin: tuple[float, float] | None 51 if origin_widget is not None: 52 self._transition_out = 'out_scale' 53 scale_origin = origin_widget.get_screen_space_center() 54 transition = 'in_scale' 55 else: 56 self._transition_out = 'out_right' 57 scale_origin = None 58 59 self._r = 'playerProfilesWindow' 60 61 # Ensure we've got an account-profile in cases where we're signed in. 62 assert bui.app.classic is not None 63 bui.app.classic.accounts.ensure_have_account_player_profile() 64 65 top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 66 67 super().__init__( 68 root_widget=bui.containerwidget( 69 size=(self._width, self._height + top_extra), 70 transition=transition, 71 scale_origin_stack_offset=scale_origin, 72 scale=( 73 2.2 74 if uiscale is bui.UIScale.SMALL 75 else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0 76 ), 77 stack_offset=( 78 (0, -14) if uiscale is bui.UIScale.SMALL else (0, 0) 79 ), 80 ) 81 ) 82 83 self._back_button = btn = bui.buttonwidget( 84 parent=self._root_widget, 85 position=(40 + x_inset, self._height - 59), 86 size=(120, 60), 87 scale=0.8, 88 label=back_label, 89 button_type='back' if self._in_main_menu else None, 90 autoselect=True, 91 on_activate_call=self._back, 92 ) 93 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 94 95 bui.textwidget( 96 parent=self._root_widget, 97 position=(self._width * 0.5, self._height - 36), 98 size=(0, 0), 99 text=bui.Lstr(resource=self._r + '.titleText'), 100 maxwidth=300, 101 color=bui.app.ui_v1.title_color, 102 scale=0.9, 103 h_align='center', 104 v_align='center', 105 ) 106 107 if self._in_main_menu: 108 bui.buttonwidget( 109 edit=btn, 110 button_type='backSmall', 111 size=(60, 60), 112 label=bui.charstr(bui.SpecialChar.BACK), 113 ) 114 115 scroll_height = self._height - 140.0 116 self._scroll_width = self._width - (188 + x_inset * 2) 117 v = self._height - 84.0 118 h = 50 + x_inset 119 b_color = (0.6, 0.53, 0.63) 120 121 scl = ( 122 1.055 123 if uiscale is bui.UIScale.SMALL 124 else 1.18 if uiscale is bui.UIScale.MEDIUM else 1.3 125 ) 126 v -= 70.0 * scl 127 self._new_button = bui.buttonwidget( 128 parent=self._root_widget, 129 position=(h, v), 130 size=(80, 66.0 * scl), 131 on_activate_call=self._new_profile, 132 color=b_color, 133 button_type='square', 134 autoselect=True, 135 textcolor=(0.75, 0.7, 0.8), 136 text_scale=0.7, 137 label=bui.Lstr(resource=self._r + '.newButtonText'), 138 ) 139 v -= 70.0 * scl 140 self._edit_button = bui.buttonwidget( 141 parent=self._root_widget, 142 position=(h, v), 143 size=(80, 66.0 * scl), 144 on_activate_call=self._edit_profile, 145 color=b_color, 146 button_type='square', 147 autoselect=True, 148 textcolor=(0.75, 0.7, 0.8), 149 text_scale=0.7, 150 label=bui.Lstr(resource=self._r + '.editButtonText'), 151 ) 152 v -= 70.0 * scl 153 self._delete_button = bui.buttonwidget( 154 parent=self._root_widget, 155 position=(h, v), 156 size=(80, 66.0 * scl), 157 on_activate_call=self._delete_profile, 158 color=b_color, 159 button_type='square', 160 autoselect=True, 161 textcolor=(0.75, 0.7, 0.8), 162 text_scale=0.7, 163 label=bui.Lstr(resource=self._r + '.deleteButtonText'), 164 ) 165 166 v = self._height - 87 167 168 bui.textwidget( 169 parent=self._root_widget, 170 position=(self._width * 0.5, self._height - 71), 171 size=(0, 0), 172 text=bui.Lstr(resource=self._r + '.explanationText'), 173 color=bui.app.ui_v1.infotextcolor, 174 maxwidth=self._width * 0.83, 175 scale=0.6, 176 h_align='center', 177 v_align='center', 178 ) 179 180 self._scrollwidget = bui.scrollwidget( 181 parent=self._root_widget, 182 highlight=False, 183 position=(140 + x_inset, v - scroll_height), 184 size=(self._scroll_width, scroll_height), 185 ) 186 bui.widget( 187 edit=self._scrollwidget, 188 autoselect=True, 189 left_widget=self._new_button, 190 ) 191 bui.containerwidget( 192 edit=self._root_widget, selected_child=self._scrollwidget 193 ) 194 self._subcontainer = bui.containerwidget( 195 parent=self._scrollwidget, 196 size=(self._scroll_width, 32), 197 background=False, 198 ) 199 v -= 255 200 self._profiles: dict[str, dict[str, Any]] | None = None 201 self._selected_profile = selected_profile 202 self._profile_widgets: list[bui.Widget] = [] 203 self._refresh() 204 self._restore_state() 205 206 def _new_profile(self) -> None: 207 # pylint: disable=cyclic-import 208 from bauiv1lib.profile.edit import EditProfileWindow 209 from bauiv1lib.purchase import PurchaseWindow 210 211 # no-op if our underlying widget is dead or on its way out. 212 if not self._root_widget or self._root_widget.transitioning_out: 213 return 214 215 plus = bui.app.plus 216 assert plus is not None 217 218 # Limit to a handful profiles if they don't have pro-options. 219 max_non_pro_profiles = plus.get_v1_account_misc_read_val('mnpp', 5) 220 assert self._profiles is not None 221 assert bui.app.classic is not None 222 if ( 223 not bui.app.classic.accounts.have_pro_options() 224 and len(self._profiles) >= max_non_pro_profiles 225 ): 226 PurchaseWindow( 227 items=['pro'], 228 header_text=bui.Lstr( 229 resource='unlockThisProfilesText', 230 subs=[('${NUM}', str(max_non_pro_profiles))], 231 ), 232 ) 233 return 234 235 # Clamp at 100 profiles (otherwise the server will and that's less 236 # elegant looking). 237 if len(self._profiles) > 100: 238 bui.screenmessage( 239 bui.Lstr( 240 translate=( 241 'serverResponses', 242 'Max number of profiles reached.', 243 ) 244 ), 245 color=(1, 0, 0), 246 ) 247 bui.getsound('error').play() 248 return 249 250 self._save_state() 251 bui.containerwidget(edit=self._root_widget, transition='out_left') 252 bui.app.ui_v1.set_main_menu_window( 253 EditProfileWindow( 254 existing_profile=None, in_main_menu=self._in_main_menu 255 ).get_root_widget(), 256 from_window=self._root_widget if self._in_main_menu else False, 257 ) 258 259 def _delete_profile(self) -> None: 260 # pylint: disable=cyclic-import 261 from bauiv1lib import confirm 262 263 if self._selected_profile is None: 264 bui.getsound('error').play() 265 bui.screenmessage( 266 bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) 267 ) 268 return 269 if self._selected_profile == '__account__': 270 bui.getsound('error').play() 271 bui.screenmessage( 272 bui.Lstr(resource=self._r + '.cantDeleteAccountProfileText'), 273 color=(1, 0, 0), 274 ) 275 return 276 confirm.ConfirmWindow( 277 bui.Lstr( 278 resource=self._r + '.deleteConfirmText', 279 subs=[('${PROFILE}', self._selected_profile)], 280 ), 281 self._do_delete_profile, 282 350, 283 ) 284 285 def _do_delete_profile(self) -> None: 286 plus = bui.app.plus 287 assert plus is not None 288 289 plus.add_v1_account_transaction( 290 {'type': 'REMOVE_PLAYER_PROFILE', 'name': self._selected_profile} 291 ) 292 plus.run_v1_account_transactions() 293 bui.getsound('shieldDown').play() 294 self._refresh() 295 296 # Select profile list. 297 bui.containerwidget( 298 edit=self._root_widget, selected_child=self._scrollwidget 299 ) 300 301 def _edit_profile(self) -> None: 302 # pylint: disable=cyclic-import 303 from bauiv1lib.profile.edit import EditProfileWindow 304 305 # no-op if our underlying widget is dead or on its way out. 306 if not self._root_widget or self._root_widget.transitioning_out: 307 return 308 309 if self._selected_profile is None: 310 bui.getsound('error').play() 311 bui.screenmessage( 312 bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) 313 ) 314 return 315 self._save_state() 316 bui.containerwidget(edit=self._root_widget, transition='out_left') 317 assert bui.app.classic is not None 318 bui.app.ui_v1.set_main_menu_window( 319 EditProfileWindow( 320 self._selected_profile, in_main_menu=self._in_main_menu 321 ).get_root_widget(), 322 from_window=self._root_widget if self._in_main_menu else False, 323 ) 324 325 def _select(self, name: str, index: int) -> None: 326 del index # Unused. 327 self._selected_profile = name 328 329 def _back(self) -> None: 330 # pylint: disable=cyclic-import 331 from bauiv1lib.account.settings import AccountSettingsWindow 332 333 # no-op if our underlying widget is dead or on its way out. 334 if not self._root_widget or self._root_widget.transitioning_out: 335 return 336 337 assert bui.app.classic is not None 338 339 self._save_state() 340 bui.containerwidget( 341 edit=self._root_widget, transition=self._transition_out 342 ) 343 if self._in_main_menu: 344 assert bui.app.classic is not None 345 bui.app.ui_v1.set_main_menu_window( 346 AccountSettingsWindow(transition='in_left').get_root_widget(), 347 from_window=self._root_widget, 348 ) 349 350 # If we're being called up standalone, handle pause/resume ourself. 351 else: 352 bui.app.classic.resume() 353 354 def _refresh(self) -> None: 355 # pylint: disable=too-many-locals 356 # pylint: disable=too-many-statements 357 from efro.util import asserttype 358 from bascenev1 import PlayerProfilesChangedMessage 359 from bascenev1lib.actor import spazappearance 360 361 assert bui.app.classic is not None 362 363 plus = bui.app.plus 364 assert plus is not None 365 366 old_selection = self._selected_profile 367 368 # Delete old. 369 while self._profile_widgets: 370 self._profile_widgets.pop().delete() 371 self._profiles = bui.app.config.get('Player Profiles', {}) 372 assert self._profiles is not None 373 items = list(self._profiles.items()) 374 items.sort(key=lambda x: asserttype(x[0], str).lower()) 375 spazzes = spazappearance.get_appearances() 376 spazzes.sort() 377 icon_textures = [ 378 bui.gettexture(bui.app.classic.spaz_appearances[s].icon_texture) 379 for s in spazzes 380 ] 381 icon_tint_textures = [ 382 bui.gettexture( 383 bui.app.classic.spaz_appearances[s].icon_mask_texture 384 ) 385 for s in spazzes 386 ] 387 index = 0 388 y_val = 35 * (len(self._profiles) - 1) 389 account_name: str | None 390 if plus.get_v1_account_state() == 'signed_in': 391 account_name = plus.get_v1_account_display_string() 392 else: 393 account_name = None 394 widget_to_select = None 395 for p_name, p_info in items: 396 if p_name == '__account__' and account_name is None: 397 continue 398 color, _highlight = bui.app.classic.get_player_profile_colors( 399 p_name 400 ) 401 scl = 1.1 402 tval = ( 403 account_name 404 if p_name == '__account__' 405 else bui.app.classic.get_player_profile_icon(p_name) + p_name 406 ) 407 408 try: 409 char_index = spazzes.index(p_info['character']) 410 except Exception: 411 char_index = spazzes.index('Spaz') 412 413 assert isinstance(tval, str) 414 txtw = bui.textwidget( 415 parent=self._subcontainer, 416 position=(5, y_val), 417 size=((self._width - 210) / scl, 28), 418 text=bui.Lstr(value=f' {tval}'), 419 h_align='left', 420 v_align='center', 421 on_select_call=bui.WeakCall(self._select, p_name, index), 422 maxwidth=self._scroll_width * 0.86, 423 corner_scale=scl, 424 color=bui.safecolor(color, 0.4), 425 always_highlight=True, 426 on_activate_call=bui.Call(self._edit_button.activate), 427 selectable=True, 428 ) 429 character = bui.imagewidget( 430 parent=self._subcontainer, 431 position=(0, y_val), 432 size=(30, 30), 433 color=(1, 1, 1), 434 mask_texture=bui.gettexture('characterIconMask'), 435 tint_color=color, 436 tint2_color=_highlight, 437 texture=icon_textures[char_index], 438 tint_texture=icon_tint_textures[char_index], 439 ) 440 if index == 0: 441 bui.widget(edit=txtw, up_widget=self._back_button) 442 if self._selected_profile is None: 443 self._selected_profile = p_name 444 bui.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40) 445 self._profile_widgets.append(txtw) 446 self._profile_widgets.append(character) 447 448 # Select/show this one if it was previously selected 449 # (but defer till after this loop since our height is 450 # still changing). 451 if p_name == old_selection: 452 widget_to_select = txtw 453 454 index += 1 455 y_val -= 35 456 457 bui.containerwidget( 458 edit=self._subcontainer, 459 size=(self._scroll_width, index * 35), 460 ) 461 if widget_to_select is not None: 462 bui.containerwidget( 463 edit=self._subcontainer, 464 selected_child=widget_to_select, 465 visible_child=widget_to_select, 466 ) 467 468 # If there's a team-chooser in existence, tell it the profile-list 469 # has probably changed. 470 session = bs.get_foreground_host_session() 471 if session is not None: 472 session.handlemessage(PlayerProfilesChangedMessage()) 473 474 def _save_state(self) -> None: 475 try: 476 sel = self._root_widget.get_selected_child() 477 if sel == self._new_button: 478 sel_name = 'New' 479 elif sel == self._edit_button: 480 sel_name = 'Edit' 481 elif sel == self._delete_button: 482 sel_name = 'Delete' 483 elif sel == self._scrollwidget: 484 sel_name = 'Scroll' 485 else: 486 sel_name = 'Back' 487 assert bui.app.classic is not None 488 bui.app.ui_v1.window_states[type(self)] = sel_name 489 except Exception: 490 logging.exception('Error saving state for %s.', self) 491 492 def _restore_state(self) -> None: 493 try: 494 assert bui.app.classic is not None 495 sel_name = bui.app.ui_v1.window_states.get(type(self)) 496 if sel_name == 'Scroll': 497 sel = self._scrollwidget 498 elif sel_name == 'New': 499 sel = self._new_button 500 elif sel_name == 'Delete': 501 sel = self._delete_button 502 elif sel_name == 'Edit': 503 sel = self._edit_button 504 elif sel_name == 'Back': 505 sel = self._back_button 506 else: 507 # By default we select our scroll widget if we have profiles; 508 # otherwise our new widget. 509 if not self._profile_widgets: 510 sel = self._new_button 511 else: 512 sel = self._scrollwidget 513 bui.containerwidget(edit=self._root_widget, selected_child=sel) 514 except Exception: 515 logging.exception('Error restoring state for %s.', self)
class
ProfileBrowserWindow(bauiv1._uitypes.Window):
18class ProfileBrowserWindow(bui.Window): 19 """Window for browsing player profiles.""" 20 21 def __init__( 22 self, 23 transition: str = 'in_right', 24 in_main_menu: bool = True, 25 selected_profile: str | None = None, 26 origin_widget: bui.Widget | None = None, 27 ): 28 # pylint: disable=too-many-statements 29 # pylint: disable=too-many-locals 30 self._in_main_menu = in_main_menu 31 if self._in_main_menu: 32 back_label = bui.Lstr(resource='backText') 33 else: 34 back_label = bui.Lstr(resource='doneText') 35 assert bui.app.classic is not None 36 uiscale = bui.app.ui_v1.uiscale 37 self._width = 800.0 if uiscale is bui.UIScale.SMALL else 600.0 38 x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0 39 self._height = ( 40 360.0 41 if uiscale is bui.UIScale.SMALL 42 else 385.0 if uiscale is bui.UIScale.MEDIUM else 410.0 43 ) 44 45 # If we're being called up standalone, handle pause/resume ourself. 46 if not self._in_main_menu: 47 assert bui.app.classic is not None 48 bui.app.classic.pause() 49 50 # If they provided an origin-widget, scale up from that. 51 scale_origin: tuple[float, float] | None 52 if origin_widget is not None: 53 self._transition_out = 'out_scale' 54 scale_origin = origin_widget.get_screen_space_center() 55 transition = 'in_scale' 56 else: 57 self._transition_out = 'out_right' 58 scale_origin = None 59 60 self._r = 'playerProfilesWindow' 61 62 # Ensure we've got an account-profile in cases where we're signed in. 63 assert bui.app.classic is not None 64 bui.app.classic.accounts.ensure_have_account_player_profile() 65 66 top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 67 68 super().__init__( 69 root_widget=bui.containerwidget( 70 size=(self._width, self._height + top_extra), 71 transition=transition, 72 scale_origin_stack_offset=scale_origin, 73 scale=( 74 2.2 75 if uiscale is bui.UIScale.SMALL 76 else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0 77 ), 78 stack_offset=( 79 (0, -14) if uiscale is bui.UIScale.SMALL else (0, 0) 80 ), 81 ) 82 ) 83 84 self._back_button = btn = bui.buttonwidget( 85 parent=self._root_widget, 86 position=(40 + x_inset, self._height - 59), 87 size=(120, 60), 88 scale=0.8, 89 label=back_label, 90 button_type='back' if self._in_main_menu else None, 91 autoselect=True, 92 on_activate_call=self._back, 93 ) 94 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 95 96 bui.textwidget( 97 parent=self._root_widget, 98 position=(self._width * 0.5, self._height - 36), 99 size=(0, 0), 100 text=bui.Lstr(resource=self._r + '.titleText'), 101 maxwidth=300, 102 color=bui.app.ui_v1.title_color, 103 scale=0.9, 104 h_align='center', 105 v_align='center', 106 ) 107 108 if self._in_main_menu: 109 bui.buttonwidget( 110 edit=btn, 111 button_type='backSmall', 112 size=(60, 60), 113 label=bui.charstr(bui.SpecialChar.BACK), 114 ) 115 116 scroll_height = self._height - 140.0 117 self._scroll_width = self._width - (188 + x_inset * 2) 118 v = self._height - 84.0 119 h = 50 + x_inset 120 b_color = (0.6, 0.53, 0.63) 121 122 scl = ( 123 1.055 124 if uiscale is bui.UIScale.SMALL 125 else 1.18 if uiscale is bui.UIScale.MEDIUM else 1.3 126 ) 127 v -= 70.0 * scl 128 self._new_button = bui.buttonwidget( 129 parent=self._root_widget, 130 position=(h, v), 131 size=(80, 66.0 * scl), 132 on_activate_call=self._new_profile, 133 color=b_color, 134 button_type='square', 135 autoselect=True, 136 textcolor=(0.75, 0.7, 0.8), 137 text_scale=0.7, 138 label=bui.Lstr(resource=self._r + '.newButtonText'), 139 ) 140 v -= 70.0 * scl 141 self._edit_button = bui.buttonwidget( 142 parent=self._root_widget, 143 position=(h, v), 144 size=(80, 66.0 * scl), 145 on_activate_call=self._edit_profile, 146 color=b_color, 147 button_type='square', 148 autoselect=True, 149 textcolor=(0.75, 0.7, 0.8), 150 text_scale=0.7, 151 label=bui.Lstr(resource=self._r + '.editButtonText'), 152 ) 153 v -= 70.0 * scl 154 self._delete_button = bui.buttonwidget( 155 parent=self._root_widget, 156 position=(h, v), 157 size=(80, 66.0 * scl), 158 on_activate_call=self._delete_profile, 159 color=b_color, 160 button_type='square', 161 autoselect=True, 162 textcolor=(0.75, 0.7, 0.8), 163 text_scale=0.7, 164 label=bui.Lstr(resource=self._r + '.deleteButtonText'), 165 ) 166 167 v = self._height - 87 168 169 bui.textwidget( 170 parent=self._root_widget, 171 position=(self._width * 0.5, self._height - 71), 172 size=(0, 0), 173 text=bui.Lstr(resource=self._r + '.explanationText'), 174 color=bui.app.ui_v1.infotextcolor, 175 maxwidth=self._width * 0.83, 176 scale=0.6, 177 h_align='center', 178 v_align='center', 179 ) 180 181 self._scrollwidget = bui.scrollwidget( 182 parent=self._root_widget, 183 highlight=False, 184 position=(140 + x_inset, v - scroll_height), 185 size=(self._scroll_width, scroll_height), 186 ) 187 bui.widget( 188 edit=self._scrollwidget, 189 autoselect=True, 190 left_widget=self._new_button, 191 ) 192 bui.containerwidget( 193 edit=self._root_widget, selected_child=self._scrollwidget 194 ) 195 self._subcontainer = bui.containerwidget( 196 parent=self._scrollwidget, 197 size=(self._scroll_width, 32), 198 background=False, 199 ) 200 v -= 255 201 self._profiles: dict[str, dict[str, Any]] | None = None 202 self._selected_profile = selected_profile 203 self._profile_widgets: list[bui.Widget] = [] 204 self._refresh() 205 self._restore_state() 206 207 def _new_profile(self) -> None: 208 # pylint: disable=cyclic-import 209 from bauiv1lib.profile.edit import EditProfileWindow 210 from bauiv1lib.purchase import PurchaseWindow 211 212 # no-op if our underlying widget is dead or on its way out. 213 if not self._root_widget or self._root_widget.transitioning_out: 214 return 215 216 plus = bui.app.plus 217 assert plus is not None 218 219 # Limit to a handful profiles if they don't have pro-options. 220 max_non_pro_profiles = plus.get_v1_account_misc_read_val('mnpp', 5) 221 assert self._profiles is not None 222 assert bui.app.classic is not None 223 if ( 224 not bui.app.classic.accounts.have_pro_options() 225 and len(self._profiles) >= max_non_pro_profiles 226 ): 227 PurchaseWindow( 228 items=['pro'], 229 header_text=bui.Lstr( 230 resource='unlockThisProfilesText', 231 subs=[('${NUM}', str(max_non_pro_profiles))], 232 ), 233 ) 234 return 235 236 # Clamp at 100 profiles (otherwise the server will and that's less 237 # elegant looking). 238 if len(self._profiles) > 100: 239 bui.screenmessage( 240 bui.Lstr( 241 translate=( 242 'serverResponses', 243 'Max number of profiles reached.', 244 ) 245 ), 246 color=(1, 0, 0), 247 ) 248 bui.getsound('error').play() 249 return 250 251 self._save_state() 252 bui.containerwidget(edit=self._root_widget, transition='out_left') 253 bui.app.ui_v1.set_main_menu_window( 254 EditProfileWindow( 255 existing_profile=None, in_main_menu=self._in_main_menu 256 ).get_root_widget(), 257 from_window=self._root_widget if self._in_main_menu else False, 258 ) 259 260 def _delete_profile(self) -> None: 261 # pylint: disable=cyclic-import 262 from bauiv1lib import confirm 263 264 if self._selected_profile is None: 265 bui.getsound('error').play() 266 bui.screenmessage( 267 bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) 268 ) 269 return 270 if self._selected_profile == '__account__': 271 bui.getsound('error').play() 272 bui.screenmessage( 273 bui.Lstr(resource=self._r + '.cantDeleteAccountProfileText'), 274 color=(1, 0, 0), 275 ) 276 return 277 confirm.ConfirmWindow( 278 bui.Lstr( 279 resource=self._r + '.deleteConfirmText', 280 subs=[('${PROFILE}', self._selected_profile)], 281 ), 282 self._do_delete_profile, 283 350, 284 ) 285 286 def _do_delete_profile(self) -> None: 287 plus = bui.app.plus 288 assert plus is not None 289 290 plus.add_v1_account_transaction( 291 {'type': 'REMOVE_PLAYER_PROFILE', 'name': self._selected_profile} 292 ) 293 plus.run_v1_account_transactions() 294 bui.getsound('shieldDown').play() 295 self._refresh() 296 297 # Select profile list. 298 bui.containerwidget( 299 edit=self._root_widget, selected_child=self._scrollwidget 300 ) 301 302 def _edit_profile(self) -> None: 303 # pylint: disable=cyclic-import 304 from bauiv1lib.profile.edit import EditProfileWindow 305 306 # no-op if our underlying widget is dead or on its way out. 307 if not self._root_widget or self._root_widget.transitioning_out: 308 return 309 310 if self._selected_profile is None: 311 bui.getsound('error').play() 312 bui.screenmessage( 313 bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) 314 ) 315 return 316 self._save_state() 317 bui.containerwidget(edit=self._root_widget, transition='out_left') 318 assert bui.app.classic is not None 319 bui.app.ui_v1.set_main_menu_window( 320 EditProfileWindow( 321 self._selected_profile, in_main_menu=self._in_main_menu 322 ).get_root_widget(), 323 from_window=self._root_widget if self._in_main_menu else False, 324 ) 325 326 def _select(self, name: str, index: int) -> None: 327 del index # Unused. 328 self._selected_profile = name 329 330 def _back(self) -> None: 331 # pylint: disable=cyclic-import 332 from bauiv1lib.account.settings import AccountSettingsWindow 333 334 # no-op if our underlying widget is dead or on its way out. 335 if not self._root_widget or self._root_widget.transitioning_out: 336 return 337 338 assert bui.app.classic is not None 339 340 self._save_state() 341 bui.containerwidget( 342 edit=self._root_widget, transition=self._transition_out 343 ) 344 if self._in_main_menu: 345 assert bui.app.classic is not None 346 bui.app.ui_v1.set_main_menu_window( 347 AccountSettingsWindow(transition='in_left').get_root_widget(), 348 from_window=self._root_widget, 349 ) 350 351 # If we're being called up standalone, handle pause/resume ourself. 352 else: 353 bui.app.classic.resume() 354 355 def _refresh(self) -> None: 356 # pylint: disable=too-many-locals 357 # pylint: disable=too-many-statements 358 from efro.util import asserttype 359 from bascenev1 import PlayerProfilesChangedMessage 360 from bascenev1lib.actor import spazappearance 361 362 assert bui.app.classic is not None 363 364 plus = bui.app.plus 365 assert plus is not None 366 367 old_selection = self._selected_profile 368 369 # Delete old. 370 while self._profile_widgets: 371 self._profile_widgets.pop().delete() 372 self._profiles = bui.app.config.get('Player Profiles', {}) 373 assert self._profiles is not None 374 items = list(self._profiles.items()) 375 items.sort(key=lambda x: asserttype(x[0], str).lower()) 376 spazzes = spazappearance.get_appearances() 377 spazzes.sort() 378 icon_textures = [ 379 bui.gettexture(bui.app.classic.spaz_appearances[s].icon_texture) 380 for s in spazzes 381 ] 382 icon_tint_textures = [ 383 bui.gettexture( 384 bui.app.classic.spaz_appearances[s].icon_mask_texture 385 ) 386 for s in spazzes 387 ] 388 index = 0 389 y_val = 35 * (len(self._profiles) - 1) 390 account_name: str | None 391 if plus.get_v1_account_state() == 'signed_in': 392 account_name = plus.get_v1_account_display_string() 393 else: 394 account_name = None 395 widget_to_select = None 396 for p_name, p_info in items: 397 if p_name == '__account__' and account_name is None: 398 continue 399 color, _highlight = bui.app.classic.get_player_profile_colors( 400 p_name 401 ) 402 scl = 1.1 403 tval = ( 404 account_name 405 if p_name == '__account__' 406 else bui.app.classic.get_player_profile_icon(p_name) + p_name 407 ) 408 409 try: 410 char_index = spazzes.index(p_info['character']) 411 except Exception: 412 char_index = spazzes.index('Spaz') 413 414 assert isinstance(tval, str) 415 txtw = bui.textwidget( 416 parent=self._subcontainer, 417 position=(5, y_val), 418 size=((self._width - 210) / scl, 28), 419 text=bui.Lstr(value=f' {tval}'), 420 h_align='left', 421 v_align='center', 422 on_select_call=bui.WeakCall(self._select, p_name, index), 423 maxwidth=self._scroll_width * 0.86, 424 corner_scale=scl, 425 color=bui.safecolor(color, 0.4), 426 always_highlight=True, 427 on_activate_call=bui.Call(self._edit_button.activate), 428 selectable=True, 429 ) 430 character = bui.imagewidget( 431 parent=self._subcontainer, 432 position=(0, y_val), 433 size=(30, 30), 434 color=(1, 1, 1), 435 mask_texture=bui.gettexture('characterIconMask'), 436 tint_color=color, 437 tint2_color=_highlight, 438 texture=icon_textures[char_index], 439 tint_texture=icon_tint_textures[char_index], 440 ) 441 if index == 0: 442 bui.widget(edit=txtw, up_widget=self._back_button) 443 if self._selected_profile is None: 444 self._selected_profile = p_name 445 bui.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40) 446 self._profile_widgets.append(txtw) 447 self._profile_widgets.append(character) 448 449 # Select/show this one if it was previously selected 450 # (but defer till after this loop since our height is 451 # still changing). 452 if p_name == old_selection: 453 widget_to_select = txtw 454 455 index += 1 456 y_val -= 35 457 458 bui.containerwidget( 459 edit=self._subcontainer, 460 size=(self._scroll_width, index * 35), 461 ) 462 if widget_to_select is not None: 463 bui.containerwidget( 464 edit=self._subcontainer, 465 selected_child=widget_to_select, 466 visible_child=widget_to_select, 467 ) 468 469 # If there's a team-chooser in existence, tell it the profile-list 470 # has probably changed. 471 session = bs.get_foreground_host_session() 472 if session is not None: 473 session.handlemessage(PlayerProfilesChangedMessage()) 474 475 def _save_state(self) -> None: 476 try: 477 sel = self._root_widget.get_selected_child() 478 if sel == self._new_button: 479 sel_name = 'New' 480 elif sel == self._edit_button: 481 sel_name = 'Edit' 482 elif sel == self._delete_button: 483 sel_name = 'Delete' 484 elif sel == self._scrollwidget: 485 sel_name = 'Scroll' 486 else: 487 sel_name = 'Back' 488 assert bui.app.classic is not None 489 bui.app.ui_v1.window_states[type(self)] = sel_name 490 except Exception: 491 logging.exception('Error saving state for %s.', self) 492 493 def _restore_state(self) -> None: 494 try: 495 assert bui.app.classic is not None 496 sel_name = bui.app.ui_v1.window_states.get(type(self)) 497 if sel_name == 'Scroll': 498 sel = self._scrollwidget 499 elif sel_name == 'New': 500 sel = self._new_button 501 elif sel_name == 'Delete': 502 sel = self._delete_button 503 elif sel_name == 'Edit': 504 sel = self._edit_button 505 elif sel_name == 'Back': 506 sel = self._back_button 507 else: 508 # By default we select our scroll widget if we have profiles; 509 # otherwise our new widget. 510 if not self._profile_widgets: 511 sel = self._new_button 512 else: 513 sel = self._scrollwidget 514 bui.containerwidget(edit=self._root_widget, selected_child=sel) 515 except Exception: 516 logging.exception('Error restoring state for %s.', self)
Window for browsing player profiles.
ProfileBrowserWindow( transition: str = 'in_right', in_main_menu: bool = True, selected_profile: str | None = None, origin_widget: _bauiv1.Widget | None = None)
21 def __init__( 22 self, 23 transition: str = 'in_right', 24 in_main_menu: bool = True, 25 selected_profile: str | None = None, 26 origin_widget: bui.Widget | None = None, 27 ): 28 # pylint: disable=too-many-statements 29 # pylint: disable=too-many-locals 30 self._in_main_menu = in_main_menu 31 if self._in_main_menu: 32 back_label = bui.Lstr(resource='backText') 33 else: 34 back_label = bui.Lstr(resource='doneText') 35 assert bui.app.classic is not None 36 uiscale = bui.app.ui_v1.uiscale 37 self._width = 800.0 if uiscale is bui.UIScale.SMALL else 600.0 38 x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0 39 self._height = ( 40 360.0 41 if uiscale is bui.UIScale.SMALL 42 else 385.0 if uiscale is bui.UIScale.MEDIUM else 410.0 43 ) 44 45 # If we're being called up standalone, handle pause/resume ourself. 46 if not self._in_main_menu: 47 assert bui.app.classic is not None 48 bui.app.classic.pause() 49 50 # If they provided an origin-widget, scale up from that. 51 scale_origin: tuple[float, float] | None 52 if origin_widget is not None: 53 self._transition_out = 'out_scale' 54 scale_origin = origin_widget.get_screen_space_center() 55 transition = 'in_scale' 56 else: 57 self._transition_out = 'out_right' 58 scale_origin = None 59 60 self._r = 'playerProfilesWindow' 61 62 # Ensure we've got an account-profile in cases where we're signed in. 63 assert bui.app.classic is not None 64 bui.app.classic.accounts.ensure_have_account_player_profile() 65 66 top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 67 68 super().__init__( 69 root_widget=bui.containerwidget( 70 size=(self._width, self._height + top_extra), 71 transition=transition, 72 scale_origin_stack_offset=scale_origin, 73 scale=( 74 2.2 75 if uiscale is bui.UIScale.SMALL 76 else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0 77 ), 78 stack_offset=( 79 (0, -14) if uiscale is bui.UIScale.SMALL else (0, 0) 80 ), 81 ) 82 ) 83 84 self._back_button = btn = bui.buttonwidget( 85 parent=self._root_widget, 86 position=(40 + x_inset, self._height - 59), 87 size=(120, 60), 88 scale=0.8, 89 label=back_label, 90 button_type='back' if self._in_main_menu else None, 91 autoselect=True, 92 on_activate_call=self._back, 93 ) 94 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 95 96 bui.textwidget( 97 parent=self._root_widget, 98 position=(self._width * 0.5, self._height - 36), 99 size=(0, 0), 100 text=bui.Lstr(resource=self._r + '.titleText'), 101 maxwidth=300, 102 color=bui.app.ui_v1.title_color, 103 scale=0.9, 104 h_align='center', 105 v_align='center', 106 ) 107 108 if self._in_main_menu: 109 bui.buttonwidget( 110 edit=btn, 111 button_type='backSmall', 112 size=(60, 60), 113 label=bui.charstr(bui.SpecialChar.BACK), 114 ) 115 116 scroll_height = self._height - 140.0 117 self._scroll_width = self._width - (188 + x_inset * 2) 118 v = self._height - 84.0 119 h = 50 + x_inset 120 b_color = (0.6, 0.53, 0.63) 121 122 scl = ( 123 1.055 124 if uiscale is bui.UIScale.SMALL 125 else 1.18 if uiscale is bui.UIScale.MEDIUM else 1.3 126 ) 127 v -= 70.0 * scl 128 self._new_button = bui.buttonwidget( 129 parent=self._root_widget, 130 position=(h, v), 131 size=(80, 66.0 * scl), 132 on_activate_call=self._new_profile, 133 color=b_color, 134 button_type='square', 135 autoselect=True, 136 textcolor=(0.75, 0.7, 0.8), 137 text_scale=0.7, 138 label=bui.Lstr(resource=self._r + '.newButtonText'), 139 ) 140 v -= 70.0 * scl 141 self._edit_button = bui.buttonwidget( 142 parent=self._root_widget, 143 position=(h, v), 144 size=(80, 66.0 * scl), 145 on_activate_call=self._edit_profile, 146 color=b_color, 147 button_type='square', 148 autoselect=True, 149 textcolor=(0.75, 0.7, 0.8), 150 text_scale=0.7, 151 label=bui.Lstr(resource=self._r + '.editButtonText'), 152 ) 153 v -= 70.0 * scl 154 self._delete_button = bui.buttonwidget( 155 parent=self._root_widget, 156 position=(h, v), 157 size=(80, 66.0 * scl), 158 on_activate_call=self._delete_profile, 159 color=b_color, 160 button_type='square', 161 autoselect=True, 162 textcolor=(0.75, 0.7, 0.8), 163 text_scale=0.7, 164 label=bui.Lstr(resource=self._r + '.deleteButtonText'), 165 ) 166 167 v = self._height - 87 168 169 bui.textwidget( 170 parent=self._root_widget, 171 position=(self._width * 0.5, self._height - 71), 172 size=(0, 0), 173 text=bui.Lstr(resource=self._r + '.explanationText'), 174 color=bui.app.ui_v1.infotextcolor, 175 maxwidth=self._width * 0.83, 176 scale=0.6, 177 h_align='center', 178 v_align='center', 179 ) 180 181 self._scrollwidget = bui.scrollwidget( 182 parent=self._root_widget, 183 highlight=False, 184 position=(140 + x_inset, v - scroll_height), 185 size=(self._scroll_width, scroll_height), 186 ) 187 bui.widget( 188 edit=self._scrollwidget, 189 autoselect=True, 190 left_widget=self._new_button, 191 ) 192 bui.containerwidget( 193 edit=self._root_widget, selected_child=self._scrollwidget 194 ) 195 self._subcontainer = bui.containerwidget( 196 parent=self._scrollwidget, 197 size=(self._scroll_width, 32), 198 background=False, 199 ) 200 v -= 255 201 self._profiles: dict[str, dict[str, Any]] | None = None 202 self._selected_profile = selected_profile 203 self._profile_widgets: list[bui.Widget] = [] 204 self._refresh() 205 self._restore_state()
Inherited Members
- bauiv1._uitypes.Window
- get_root_widget