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 = 700.0 if uiscale is bui.UIScale.SMALL else 600.0 37 x_inset = 50.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 42 if uiscale is bui.UIScale.MEDIUM 43 else 410.0 44 ) 45 46 # If we're being called up standalone, handle pause/resume ourself. 47 if not self._in_main_menu: 48 assert bui.app.classic is not None 49 bui.app.classic.pause() 50 51 # If they provided an origin-widget, scale up from that. 52 scale_origin: tuple[float, float] | None 53 if origin_widget is not None: 54 self._transition_out = 'out_scale' 55 scale_origin = origin_widget.get_screen_space_center() 56 transition = 'in_scale' 57 else: 58 self._transition_out = 'out_right' 59 scale_origin = None 60 61 self._r = 'playerProfilesWindow' 62 63 # Ensure we've got an account-profile in cases where we're signed in. 64 assert bui.app.classic is not None 65 bui.app.classic.accounts.ensure_have_account_player_profile() 66 67 top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 68 69 super().__init__( 70 root_widget=bui.containerwidget( 71 size=(self._width, self._height + top_extra), 72 transition=transition, 73 scale_origin_stack_offset=scale_origin, 74 scale=( 75 2.2 76 if uiscale is bui.UIScale.SMALL 77 else 1.6 78 if uiscale is bui.UIScale.MEDIUM 79 else 1.0 80 ), 81 stack_offset=(0, -14) 82 if uiscale is bui.UIScale.SMALL 83 else (0, 0), 84 ) 85 ) 86 87 self._back_button = btn = bui.buttonwidget( 88 parent=self._root_widget, 89 position=(40 + x_inset, self._height - 59), 90 size=(120, 60), 91 scale=0.8, 92 label=back_label, 93 button_type='back' if self._in_main_menu else None, 94 autoselect=True, 95 on_activate_call=self._back, 96 ) 97 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 98 99 bui.textwidget( 100 parent=self._root_widget, 101 position=(self._width * 0.5, self._height - 36), 102 size=(0, 0), 103 text=bui.Lstr(resource=self._r + '.titleText'), 104 maxwidth=300, 105 color=bui.app.ui_v1.title_color, 106 scale=0.9, 107 h_align='center', 108 v_align='center', 109 ) 110 111 if self._in_main_menu: 112 bui.buttonwidget( 113 edit=btn, 114 button_type='backSmall', 115 size=(60, 60), 116 label=bui.charstr(bui.SpecialChar.BACK), 117 ) 118 119 scroll_height = self._height - 140.0 120 self._scroll_width = self._width - (188 + x_inset * 2) 121 v = self._height - 84.0 122 h = 50 + x_inset 123 b_color = (0.6, 0.53, 0.63) 124 125 scl = ( 126 1.055 127 if uiscale is bui.UIScale.SMALL 128 else 1.18 129 if uiscale is bui.UIScale.MEDIUM 130 else 1.3 131 ) 132 v -= 70.0 * scl 133 self._new_button = bui.buttonwidget( 134 parent=self._root_widget, 135 position=(h, v), 136 size=(80, 66.0 * scl), 137 on_activate_call=self._new_profile, 138 color=b_color, 139 button_type='square', 140 autoselect=True, 141 textcolor=(0.75, 0.7, 0.8), 142 text_scale=0.7, 143 label=bui.Lstr(resource=self._r + '.newButtonText'), 144 ) 145 v -= 70.0 * scl 146 self._edit_button = bui.buttonwidget( 147 parent=self._root_widget, 148 position=(h, v), 149 size=(80, 66.0 * scl), 150 on_activate_call=self._edit_profile, 151 color=b_color, 152 button_type='square', 153 autoselect=True, 154 textcolor=(0.75, 0.7, 0.8), 155 text_scale=0.7, 156 label=bui.Lstr(resource=self._r + '.editButtonText'), 157 ) 158 v -= 70.0 * scl 159 self._delete_button = bui.buttonwidget( 160 parent=self._root_widget, 161 position=(h, v), 162 size=(80, 66.0 * scl), 163 on_activate_call=self._delete_profile, 164 color=b_color, 165 button_type='square', 166 autoselect=True, 167 textcolor=(0.75, 0.7, 0.8), 168 text_scale=0.7, 169 label=bui.Lstr(resource=self._r + '.deleteButtonText'), 170 ) 171 172 v = self._height - 87 173 174 bui.textwidget( 175 parent=self._root_widget, 176 position=(self._width * 0.5, self._height - 71), 177 size=(0, 0), 178 text=bui.Lstr(resource=self._r + '.explanationText'), 179 color=bui.app.ui_v1.infotextcolor, 180 maxwidth=self._width * 0.83, 181 scale=0.6, 182 h_align='center', 183 v_align='center', 184 ) 185 186 self._scrollwidget = bui.scrollwidget( 187 parent=self._root_widget, 188 highlight=False, 189 position=(140 + x_inset, v - scroll_height), 190 size=(self._scroll_width, scroll_height), 191 ) 192 bui.widget( 193 edit=self._scrollwidget, 194 autoselect=True, 195 left_widget=self._new_button, 196 ) 197 bui.containerwidget( 198 edit=self._root_widget, selected_child=self._scrollwidget 199 ) 200 self._columnwidget = bui.columnwidget( 201 parent=self._scrollwidget, border=2, margin=0 202 ) 203 v -= 255 204 self._profiles: dict[str, dict[str, Any]] | None = None 205 self._selected_profile = selected_profile 206 self._profile_widgets: list[bui.Widget] = [] 207 self._refresh() 208 self._restore_state() 209 210 def _new_profile(self) -> None: 211 # pylint: disable=cyclic-import 212 from bauiv1lib.profile.edit import EditProfileWindow 213 from bauiv1lib.purchase import PurchaseWindow 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 ) 257 258 def _delete_profile(self) -> None: 259 # pylint: disable=cyclic-import 260 from bauiv1lib import confirm 261 262 if self._selected_profile is None: 263 bui.getsound('error').play() 264 bui.screenmessage( 265 bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) 266 ) 267 return 268 if self._selected_profile == '__account__': 269 bui.getsound('error').play() 270 bui.screenmessage( 271 bui.Lstr(resource=self._r + '.cantDeleteAccountProfileText'), 272 color=(1, 0, 0), 273 ) 274 return 275 confirm.ConfirmWindow( 276 bui.Lstr( 277 resource=self._r + '.deleteConfirmText', 278 subs=[('${PROFILE}', self._selected_profile)], 279 ), 280 self._do_delete_profile, 281 350, 282 ) 283 284 def _do_delete_profile(self) -> None: 285 plus = bui.app.plus 286 assert plus is not None 287 288 plus.add_v1_account_transaction( 289 {'type': 'REMOVE_PLAYER_PROFILE', 'name': self._selected_profile} 290 ) 291 plus.run_v1_account_transactions() 292 bui.getsound('shieldDown').play() 293 self._refresh() 294 295 # Select profile list. 296 bui.containerwidget( 297 edit=self._root_widget, selected_child=self._scrollwidget 298 ) 299 300 def _edit_profile(self) -> None: 301 # pylint: disable=cyclic-import 302 from bauiv1lib.profile.edit import EditProfileWindow 303 304 if self._selected_profile is None: 305 bui.getsound('error').play() 306 bui.screenmessage( 307 bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) 308 ) 309 return 310 self._save_state() 311 bui.containerwidget(edit=self._root_widget, transition='out_left') 312 assert bui.app.classic is not None 313 bui.app.ui_v1.set_main_menu_window( 314 EditProfileWindow( 315 self._selected_profile, in_main_menu=self._in_main_menu 316 ).get_root_widget() 317 ) 318 319 def _select(self, name: str, index: int) -> None: 320 del index # Unused. 321 self._selected_profile = name 322 323 def _back(self) -> None: 324 # pylint: disable=cyclic-import 325 from bauiv1lib.account.settings import AccountSettingsWindow 326 327 assert bui.app.classic is not None 328 329 self._save_state() 330 bui.containerwidget( 331 edit=self._root_widget, transition=self._transition_out 332 ) 333 if self._in_main_menu: 334 assert bui.app.classic is not None 335 bui.app.ui_v1.set_main_menu_window( 336 AccountSettingsWindow(transition='in_left').get_root_widget() 337 ) 338 339 # If we're being called up standalone, handle pause/resume ourself. 340 else: 341 bui.app.classic.resume() 342 343 def _refresh(self) -> None: 344 # pylint: disable=too-many-locals 345 from efro.util import asserttype 346 from bascenev1 import PlayerProfilesChangedMessage 347 348 assert bui.app.classic is not None 349 350 plus = bui.app.plus 351 assert plus is not None 352 353 old_selection = self._selected_profile 354 355 # Delete old. 356 while self._profile_widgets: 357 self._profile_widgets.pop().delete() 358 self._profiles = bui.app.config.get('Player Profiles', {}) 359 assert self._profiles is not None 360 items = list(self._profiles.items()) 361 items.sort(key=lambda x: asserttype(x[0], str).lower()) 362 index = 0 363 account_name: str | None 364 if plus.get_v1_account_state() == 'signed_in': 365 account_name = plus.get_v1_account_display_string() 366 else: 367 account_name = None 368 widget_to_select = None 369 for p_name, _ in items: 370 if p_name == '__account__' and account_name is None: 371 continue 372 color, _highlight = bui.app.classic.get_player_profile_colors( 373 p_name 374 ) 375 scl = 1.1 376 tval = ( 377 account_name 378 if p_name == '__account__' 379 else bui.app.classic.get_player_profile_icon(p_name) + p_name 380 ) 381 assert isinstance(tval, str) 382 txtw = bui.textwidget( 383 parent=self._columnwidget, 384 position=(0, 32), 385 size=((self._width - 40) / scl, 28), 386 text=bui.Lstr(value=tval), 387 h_align='left', 388 v_align='center', 389 on_select_call=bui.WeakCall(self._select, p_name, index), 390 maxwidth=self._scroll_width * 0.92, 391 corner_scale=scl, 392 color=bui.safecolor(color, 0.4), 393 always_highlight=True, 394 on_activate_call=bui.Call(self._edit_button.activate), 395 selectable=True, 396 ) 397 if index == 0: 398 bui.widget(edit=txtw, up_widget=self._back_button) 399 bui.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40) 400 self._profile_widgets.append(txtw) 401 402 # Select/show this one if it was previously selected 403 # (but defer till after this loop since our height is 404 # still changing). 405 if p_name == old_selection: 406 widget_to_select = txtw 407 408 index += 1 409 410 if widget_to_select is not None: 411 bui.columnwidget( 412 edit=self._columnwidget, 413 selected_child=widget_to_select, 414 visible_child=widget_to_select, 415 ) 416 417 # If there's a team-chooser in existence, tell it the profile-list 418 # has probably changed. 419 session = bs.get_foreground_host_session() 420 if session is not None: 421 session.handlemessage(PlayerProfilesChangedMessage()) 422 423 def _save_state(self) -> None: 424 try: 425 sel = self._root_widget.get_selected_child() 426 if sel == self._new_button: 427 sel_name = 'New' 428 elif sel == self._edit_button: 429 sel_name = 'Edit' 430 elif sel == self._delete_button: 431 sel_name = 'Delete' 432 elif sel == self._scrollwidget: 433 sel_name = 'Scroll' 434 else: 435 sel_name = 'Back' 436 assert bui.app.classic is not None 437 bui.app.ui_v1.window_states[type(self)] = sel_name 438 except Exception: 439 logging.exception('Error saving state for %s.', self) 440 441 def _restore_state(self) -> None: 442 try: 443 assert bui.app.classic is not None 444 sel_name = bui.app.ui_v1.window_states.get(type(self)) 445 if sel_name == 'Scroll': 446 sel = self._scrollwidget 447 elif sel_name == 'New': 448 sel = self._new_button 449 elif sel_name == 'Delete': 450 sel = self._delete_button 451 elif sel_name == 'Edit': 452 sel = self._edit_button 453 elif sel_name == 'Back': 454 sel = self._back_button 455 else: 456 # By default we select our scroll widget if we have profiles; 457 # otherwise our new widget. 458 if not self._profile_widgets: 459 sel = self._new_button 460 else: 461 sel = self._scrollwidget 462 bui.containerwidget(edit=self._root_widget, selected_child=sel) 463 except Exception: 464 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 = 700.0 if uiscale is bui.UIScale.SMALL else 600.0 38 x_inset = 50.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 43 if uiscale is bui.UIScale.MEDIUM 44 else 410.0 45 ) 46 47 # If we're being called up standalone, handle pause/resume ourself. 48 if not self._in_main_menu: 49 assert bui.app.classic is not None 50 bui.app.classic.pause() 51 52 # If they provided an origin-widget, scale up from that. 53 scale_origin: tuple[float, float] | None 54 if origin_widget is not None: 55 self._transition_out = 'out_scale' 56 scale_origin = origin_widget.get_screen_space_center() 57 transition = 'in_scale' 58 else: 59 self._transition_out = 'out_right' 60 scale_origin = None 61 62 self._r = 'playerProfilesWindow' 63 64 # Ensure we've got an account-profile in cases where we're signed in. 65 assert bui.app.classic is not None 66 bui.app.classic.accounts.ensure_have_account_player_profile() 67 68 top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 69 70 super().__init__( 71 root_widget=bui.containerwidget( 72 size=(self._width, self._height + top_extra), 73 transition=transition, 74 scale_origin_stack_offset=scale_origin, 75 scale=( 76 2.2 77 if uiscale is bui.UIScale.SMALL 78 else 1.6 79 if uiscale is bui.UIScale.MEDIUM 80 else 1.0 81 ), 82 stack_offset=(0, -14) 83 if uiscale is bui.UIScale.SMALL 84 else (0, 0), 85 ) 86 ) 87 88 self._back_button = btn = bui.buttonwidget( 89 parent=self._root_widget, 90 position=(40 + x_inset, self._height - 59), 91 size=(120, 60), 92 scale=0.8, 93 label=back_label, 94 button_type='back' if self._in_main_menu else None, 95 autoselect=True, 96 on_activate_call=self._back, 97 ) 98 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 99 100 bui.textwidget( 101 parent=self._root_widget, 102 position=(self._width * 0.5, self._height - 36), 103 size=(0, 0), 104 text=bui.Lstr(resource=self._r + '.titleText'), 105 maxwidth=300, 106 color=bui.app.ui_v1.title_color, 107 scale=0.9, 108 h_align='center', 109 v_align='center', 110 ) 111 112 if self._in_main_menu: 113 bui.buttonwidget( 114 edit=btn, 115 button_type='backSmall', 116 size=(60, 60), 117 label=bui.charstr(bui.SpecialChar.BACK), 118 ) 119 120 scroll_height = self._height - 140.0 121 self._scroll_width = self._width - (188 + x_inset * 2) 122 v = self._height - 84.0 123 h = 50 + x_inset 124 b_color = (0.6, 0.53, 0.63) 125 126 scl = ( 127 1.055 128 if uiscale is bui.UIScale.SMALL 129 else 1.18 130 if uiscale is bui.UIScale.MEDIUM 131 else 1.3 132 ) 133 v -= 70.0 * scl 134 self._new_button = bui.buttonwidget( 135 parent=self._root_widget, 136 position=(h, v), 137 size=(80, 66.0 * scl), 138 on_activate_call=self._new_profile, 139 color=b_color, 140 button_type='square', 141 autoselect=True, 142 textcolor=(0.75, 0.7, 0.8), 143 text_scale=0.7, 144 label=bui.Lstr(resource=self._r + '.newButtonText'), 145 ) 146 v -= 70.0 * scl 147 self._edit_button = bui.buttonwidget( 148 parent=self._root_widget, 149 position=(h, v), 150 size=(80, 66.0 * scl), 151 on_activate_call=self._edit_profile, 152 color=b_color, 153 button_type='square', 154 autoselect=True, 155 textcolor=(0.75, 0.7, 0.8), 156 text_scale=0.7, 157 label=bui.Lstr(resource=self._r + '.editButtonText'), 158 ) 159 v -= 70.0 * scl 160 self._delete_button = bui.buttonwidget( 161 parent=self._root_widget, 162 position=(h, v), 163 size=(80, 66.0 * scl), 164 on_activate_call=self._delete_profile, 165 color=b_color, 166 button_type='square', 167 autoselect=True, 168 textcolor=(0.75, 0.7, 0.8), 169 text_scale=0.7, 170 label=bui.Lstr(resource=self._r + '.deleteButtonText'), 171 ) 172 173 v = self._height - 87 174 175 bui.textwidget( 176 parent=self._root_widget, 177 position=(self._width * 0.5, self._height - 71), 178 size=(0, 0), 179 text=bui.Lstr(resource=self._r + '.explanationText'), 180 color=bui.app.ui_v1.infotextcolor, 181 maxwidth=self._width * 0.83, 182 scale=0.6, 183 h_align='center', 184 v_align='center', 185 ) 186 187 self._scrollwidget = bui.scrollwidget( 188 parent=self._root_widget, 189 highlight=False, 190 position=(140 + x_inset, v - scroll_height), 191 size=(self._scroll_width, scroll_height), 192 ) 193 bui.widget( 194 edit=self._scrollwidget, 195 autoselect=True, 196 left_widget=self._new_button, 197 ) 198 bui.containerwidget( 199 edit=self._root_widget, selected_child=self._scrollwidget 200 ) 201 self._columnwidget = bui.columnwidget( 202 parent=self._scrollwidget, border=2, margin=0 203 ) 204 v -= 255 205 self._profiles: dict[str, dict[str, Any]] | None = None 206 self._selected_profile = selected_profile 207 self._profile_widgets: list[bui.Widget] = [] 208 self._refresh() 209 self._restore_state() 210 211 def _new_profile(self) -> None: 212 # pylint: disable=cyclic-import 213 from bauiv1lib.profile.edit import EditProfileWindow 214 from bauiv1lib.purchase import PurchaseWindow 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 ) 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 if self._selected_profile is None: 306 bui.getsound('error').play() 307 bui.screenmessage( 308 bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) 309 ) 310 return 311 self._save_state() 312 bui.containerwidget(edit=self._root_widget, transition='out_left') 313 assert bui.app.classic is not None 314 bui.app.ui_v1.set_main_menu_window( 315 EditProfileWindow( 316 self._selected_profile, in_main_menu=self._in_main_menu 317 ).get_root_widget() 318 ) 319 320 def _select(self, name: str, index: int) -> None: 321 del index # Unused. 322 self._selected_profile = name 323 324 def _back(self) -> None: 325 # pylint: disable=cyclic-import 326 from bauiv1lib.account.settings import AccountSettingsWindow 327 328 assert bui.app.classic is not None 329 330 self._save_state() 331 bui.containerwidget( 332 edit=self._root_widget, transition=self._transition_out 333 ) 334 if self._in_main_menu: 335 assert bui.app.classic is not None 336 bui.app.ui_v1.set_main_menu_window( 337 AccountSettingsWindow(transition='in_left').get_root_widget() 338 ) 339 340 # If we're being called up standalone, handle pause/resume ourself. 341 else: 342 bui.app.classic.resume() 343 344 def _refresh(self) -> None: 345 # pylint: disable=too-many-locals 346 from efro.util import asserttype 347 from bascenev1 import PlayerProfilesChangedMessage 348 349 assert bui.app.classic is not None 350 351 plus = bui.app.plus 352 assert plus is not None 353 354 old_selection = self._selected_profile 355 356 # Delete old. 357 while self._profile_widgets: 358 self._profile_widgets.pop().delete() 359 self._profiles = bui.app.config.get('Player Profiles', {}) 360 assert self._profiles is not None 361 items = list(self._profiles.items()) 362 items.sort(key=lambda x: asserttype(x[0], str).lower()) 363 index = 0 364 account_name: str | None 365 if plus.get_v1_account_state() == 'signed_in': 366 account_name = plus.get_v1_account_display_string() 367 else: 368 account_name = None 369 widget_to_select = None 370 for p_name, _ in items: 371 if p_name == '__account__' and account_name is None: 372 continue 373 color, _highlight = bui.app.classic.get_player_profile_colors( 374 p_name 375 ) 376 scl = 1.1 377 tval = ( 378 account_name 379 if p_name == '__account__' 380 else bui.app.classic.get_player_profile_icon(p_name) + p_name 381 ) 382 assert isinstance(tval, str) 383 txtw = bui.textwidget( 384 parent=self._columnwidget, 385 position=(0, 32), 386 size=((self._width - 40) / scl, 28), 387 text=bui.Lstr(value=tval), 388 h_align='left', 389 v_align='center', 390 on_select_call=bui.WeakCall(self._select, p_name, index), 391 maxwidth=self._scroll_width * 0.92, 392 corner_scale=scl, 393 color=bui.safecolor(color, 0.4), 394 always_highlight=True, 395 on_activate_call=bui.Call(self._edit_button.activate), 396 selectable=True, 397 ) 398 if index == 0: 399 bui.widget(edit=txtw, up_widget=self._back_button) 400 bui.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40) 401 self._profile_widgets.append(txtw) 402 403 # Select/show this one if it was previously selected 404 # (but defer till after this loop since our height is 405 # still changing). 406 if p_name == old_selection: 407 widget_to_select = txtw 408 409 index += 1 410 411 if widget_to_select is not None: 412 bui.columnwidget( 413 edit=self._columnwidget, 414 selected_child=widget_to_select, 415 visible_child=widget_to_select, 416 ) 417 418 # If there's a team-chooser in existence, tell it the profile-list 419 # has probably changed. 420 session = bs.get_foreground_host_session() 421 if session is not None: 422 session.handlemessage(PlayerProfilesChangedMessage()) 423 424 def _save_state(self) -> None: 425 try: 426 sel = self._root_widget.get_selected_child() 427 if sel == self._new_button: 428 sel_name = 'New' 429 elif sel == self._edit_button: 430 sel_name = 'Edit' 431 elif sel == self._delete_button: 432 sel_name = 'Delete' 433 elif sel == self._scrollwidget: 434 sel_name = 'Scroll' 435 else: 436 sel_name = 'Back' 437 assert bui.app.classic is not None 438 bui.app.ui_v1.window_states[type(self)] = sel_name 439 except Exception: 440 logging.exception('Error saving state for %s.', self) 441 442 def _restore_state(self) -> None: 443 try: 444 assert bui.app.classic is not None 445 sel_name = bui.app.ui_v1.window_states.get(type(self)) 446 if sel_name == 'Scroll': 447 sel = self._scrollwidget 448 elif sel_name == 'New': 449 sel = self._new_button 450 elif sel_name == 'Delete': 451 sel = self._delete_button 452 elif sel_name == 'Edit': 453 sel = self._edit_button 454 elif sel_name == 'Back': 455 sel = self._back_button 456 else: 457 # By default we select our scroll widget if we have profiles; 458 # otherwise our new widget. 459 if not self._profile_widgets: 460 sel = self._new_button 461 else: 462 sel = self._scrollwidget 463 bui.containerwidget(edit=self._root_widget, selected_child=sel) 464 except Exception: 465 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 = 700.0 if uiscale is bui.UIScale.SMALL else 600.0 38 x_inset = 50.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 43 if uiscale is bui.UIScale.MEDIUM 44 else 410.0 45 ) 46 47 # If we're being called up standalone, handle pause/resume ourself. 48 if not self._in_main_menu: 49 assert bui.app.classic is not None 50 bui.app.classic.pause() 51 52 # If they provided an origin-widget, scale up from that. 53 scale_origin: tuple[float, float] | None 54 if origin_widget is not None: 55 self._transition_out = 'out_scale' 56 scale_origin = origin_widget.get_screen_space_center() 57 transition = 'in_scale' 58 else: 59 self._transition_out = 'out_right' 60 scale_origin = None 61 62 self._r = 'playerProfilesWindow' 63 64 # Ensure we've got an account-profile in cases where we're signed in. 65 assert bui.app.classic is not None 66 bui.app.classic.accounts.ensure_have_account_player_profile() 67 68 top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 69 70 super().__init__( 71 root_widget=bui.containerwidget( 72 size=(self._width, self._height + top_extra), 73 transition=transition, 74 scale_origin_stack_offset=scale_origin, 75 scale=( 76 2.2 77 if uiscale is bui.UIScale.SMALL 78 else 1.6 79 if uiscale is bui.UIScale.MEDIUM 80 else 1.0 81 ), 82 stack_offset=(0, -14) 83 if uiscale is bui.UIScale.SMALL 84 else (0, 0), 85 ) 86 ) 87 88 self._back_button = btn = bui.buttonwidget( 89 parent=self._root_widget, 90 position=(40 + x_inset, self._height - 59), 91 size=(120, 60), 92 scale=0.8, 93 label=back_label, 94 button_type='back' if self._in_main_menu else None, 95 autoselect=True, 96 on_activate_call=self._back, 97 ) 98 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 99 100 bui.textwidget( 101 parent=self._root_widget, 102 position=(self._width * 0.5, self._height - 36), 103 size=(0, 0), 104 text=bui.Lstr(resource=self._r + '.titleText'), 105 maxwidth=300, 106 color=bui.app.ui_v1.title_color, 107 scale=0.9, 108 h_align='center', 109 v_align='center', 110 ) 111 112 if self._in_main_menu: 113 bui.buttonwidget( 114 edit=btn, 115 button_type='backSmall', 116 size=(60, 60), 117 label=bui.charstr(bui.SpecialChar.BACK), 118 ) 119 120 scroll_height = self._height - 140.0 121 self._scroll_width = self._width - (188 + x_inset * 2) 122 v = self._height - 84.0 123 h = 50 + x_inset 124 b_color = (0.6, 0.53, 0.63) 125 126 scl = ( 127 1.055 128 if uiscale is bui.UIScale.SMALL 129 else 1.18 130 if uiscale is bui.UIScale.MEDIUM 131 else 1.3 132 ) 133 v -= 70.0 * scl 134 self._new_button = bui.buttonwidget( 135 parent=self._root_widget, 136 position=(h, v), 137 size=(80, 66.0 * scl), 138 on_activate_call=self._new_profile, 139 color=b_color, 140 button_type='square', 141 autoselect=True, 142 textcolor=(0.75, 0.7, 0.8), 143 text_scale=0.7, 144 label=bui.Lstr(resource=self._r + '.newButtonText'), 145 ) 146 v -= 70.0 * scl 147 self._edit_button = bui.buttonwidget( 148 parent=self._root_widget, 149 position=(h, v), 150 size=(80, 66.0 * scl), 151 on_activate_call=self._edit_profile, 152 color=b_color, 153 button_type='square', 154 autoselect=True, 155 textcolor=(0.75, 0.7, 0.8), 156 text_scale=0.7, 157 label=bui.Lstr(resource=self._r + '.editButtonText'), 158 ) 159 v -= 70.0 * scl 160 self._delete_button = bui.buttonwidget( 161 parent=self._root_widget, 162 position=(h, v), 163 size=(80, 66.0 * scl), 164 on_activate_call=self._delete_profile, 165 color=b_color, 166 button_type='square', 167 autoselect=True, 168 textcolor=(0.75, 0.7, 0.8), 169 text_scale=0.7, 170 label=bui.Lstr(resource=self._r + '.deleteButtonText'), 171 ) 172 173 v = self._height - 87 174 175 bui.textwidget( 176 parent=self._root_widget, 177 position=(self._width * 0.5, self._height - 71), 178 size=(0, 0), 179 text=bui.Lstr(resource=self._r + '.explanationText'), 180 color=bui.app.ui_v1.infotextcolor, 181 maxwidth=self._width * 0.83, 182 scale=0.6, 183 h_align='center', 184 v_align='center', 185 ) 186 187 self._scrollwidget = bui.scrollwidget( 188 parent=self._root_widget, 189 highlight=False, 190 position=(140 + x_inset, v - scroll_height), 191 size=(self._scroll_width, scroll_height), 192 ) 193 bui.widget( 194 edit=self._scrollwidget, 195 autoselect=True, 196 left_widget=self._new_button, 197 ) 198 bui.containerwidget( 199 edit=self._root_widget, selected_child=self._scrollwidget 200 ) 201 self._columnwidget = bui.columnwidget( 202 parent=self._scrollwidget, border=2, margin=0 203 ) 204 v -= 255 205 self._profiles: dict[str, dict[str, Any]] | None = None 206 self._selected_profile = selected_profile 207 self._profile_widgets: list[bui.Widget] = [] 208 self._refresh() 209 self._restore_state()
Inherited Members
- bauiv1._uitypes.Window
- get_root_widget