bauiv1lib.account.viewer
Provides a popup for displaying info about any account.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides a popup for displaying info about any account.""" 4 5from __future__ import annotations 6 7from typing import TYPE_CHECKING, override 8import logging 9 10import bauiv1 as bui 11 12from bauiv1lib.popup import PopupWindow, PopupMenuWindow 13 14if TYPE_CHECKING: 15 from typing import Any 16 17 from bauiv1lib.popup import PopupMenu 18 19 20class AccountViewerWindow(PopupWindow): 21 """Popup window that displays info for an account.""" 22 23 def __init__( 24 self, 25 account_id: str, 26 *, 27 profile_id: str | None = None, 28 position: tuple[float, float] = (0.0, 0.0), 29 scale: float | None = None, 30 offset: tuple[float, float] = (0.0, 0.0), 31 ): 32 if bui.app.classic is None: 33 raise RuntimeError('This requires classic support.') 34 35 plus = bui.app.plus 36 assert plus is not None 37 38 self._account_id = account_id 39 self._profile_id = profile_id 40 41 uiscale = bui.app.ui_v1.uiscale 42 if scale is None: 43 scale = ( 44 2.6 45 if uiscale is bui.UIScale.SMALL 46 else 1.8 if uiscale is bui.UIScale.MEDIUM else 1.4 47 ) 48 self._transitioning_out = False 49 50 self._width = 400 51 self._height = ( 52 300 53 if uiscale is bui.UIScale.SMALL 54 else 400 if uiscale is bui.UIScale.MEDIUM else 450 55 ) 56 self._subcontainer: bui.Widget | None = None 57 58 bg_color = (0.5, 0.4, 0.6) 59 60 # Creates our _root_widget. 61 super().__init__( 62 position=position, 63 size=(self._width, self._height), 64 scale=scale, 65 bg_color=bg_color, 66 offset=offset, 67 ) 68 69 self._cancel_button = bui.buttonwidget( 70 parent=self.root_widget, 71 position=(50, self._height - 30), 72 size=(50, 50), 73 scale=0.5, 74 label='', 75 color=bg_color, 76 on_activate_call=self._on_cancel_press, 77 autoselect=True, 78 icon=bui.gettexture('crossOut'), 79 iconscale=1.2, 80 ) 81 82 self._title_text = bui.textwidget( 83 parent=self.root_widget, 84 position=(self._width * 0.5, self._height - 20), 85 size=(0, 0), 86 h_align='center', 87 v_align='center', 88 scale=0.6, 89 text=bui.Lstr(resource='playerInfoText'), 90 maxwidth=200, 91 color=bui.app.ui_v1.title_color, 92 ) 93 94 self._scrollwidget = bui.scrollwidget( 95 parent=self.root_widget, 96 size=(self._width - 60, self._height - 70), 97 position=(30, 30), 98 capture_arrows=True, 99 simple_culling_v=10, 100 border_opacity=0.4, 101 ) 102 bui.widget(edit=self._scrollwidget, autoselect=True) 103 104 # Note to self: Make sure to always update loading text and 105 # spinner visibility together. 106 self._loading_text = bui.textwidget( 107 parent=self._scrollwidget, 108 scale=0.5, 109 text='', 110 size=(self._width - 60, 100), 111 h_align='center', 112 v_align='center', 113 ) 114 self._loading_spinner = bui.spinnerwidget( 115 parent=self.root_widget, 116 position=(self._width * 0.5, self._height * 0.5), 117 style='bomb', 118 size=48, 119 ) 120 121 # In cases where the user most likely has a browser/email, lets 122 # offer a 'report this user' button. 123 if ( 124 bui.is_browser_likely_available() 125 and plus.get_v1_account_misc_read_val( 126 'showAccountExtrasMenu', False 127 ) 128 ): 129 self._extras_menu_button = bui.buttonwidget( 130 parent=self.root_widget, 131 size=(20, 20), 132 position=(self._width - 60, self._height - 30), 133 autoselect=True, 134 label='...', 135 button_type='square', 136 color=(0.64, 0.52, 0.69), 137 textcolor=(0.57, 0.47, 0.57), 138 on_activate_call=self._on_extras_menu_press, 139 ) 140 141 bui.containerwidget( 142 edit=self.root_widget, cancel_button=self._cancel_button 143 ) 144 145 bui.app.classic.master_server_v1_get( 146 'bsAccountInfo', 147 { 148 'buildNumber': bui.app.env.engine_build_number, 149 'accountID': self._account_id, 150 'profileID': self._profile_id, 151 }, 152 callback=bui.WeakCall(self._on_query_response), 153 ) 154 155 def popup_menu_selected_choice( 156 self, window: PopupMenu, choice: str 157 ) -> None: 158 """Called when a menu entry is selected.""" 159 del window # Unused arg. 160 if choice == 'more': 161 self._on_more_press() 162 elif choice == 'report': 163 self._on_report_press() 164 elif choice == 'ban': 165 self._on_ban_press() 166 else: 167 print('ERROR: unknown account info extras menu item:', choice) 168 169 def popup_menu_closing(self, window: PopupMenu) -> None: 170 """Called when the popup menu is closing.""" 171 172 def _on_extras_menu_press(self) -> None: 173 choices = ['more', 'report'] 174 choices_display = [ 175 bui.Lstr(resource='coopSelectWindow.seeMoreText'), 176 bui.Lstr(resource='reportThisPlayerText'), 177 ] 178 is_admin = False 179 if is_admin: 180 bui.screenmessage('TEMP FORCING ADMIN ON') 181 choices.append('ban') 182 choices_display.append(bui.Lstr(resource='banThisPlayerText')) 183 184 assert bui.app.classic is not None 185 uiscale = bui.app.ui_v1.uiscale 186 PopupMenuWindow( 187 position=self._extras_menu_button.get_screen_space_center(), 188 scale=( 189 2.3 190 if uiscale is bui.UIScale.SMALL 191 else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 192 ), 193 choices=choices, 194 choices_display=choices_display, 195 current_choice='more', 196 delegate=self, 197 ) 198 199 def _on_ban_press(self) -> None: 200 plus = bui.app.plus 201 assert plus is not None 202 203 plus.add_v1_account_transaction( 204 {'type': 'BAN_ACCOUNT', 'account': self._account_id} 205 ) 206 plus.run_v1_account_transactions() 207 208 def _on_report_press(self) -> None: 209 from bauiv1lib import report 210 211 report.ReportPlayerWindow( 212 self._account_id, origin_widget=self._extras_menu_button 213 ) 214 215 def _on_more_press(self) -> None: 216 plus = bui.app.plus 217 assert plus is not None 218 bui.open_url( 219 plus.get_master_server_address() 220 + '/highscores?profile=' 221 + self._account_id 222 ) 223 224 def _on_query_response(self, data: dict[str, Any] | None) -> None: 225 # FIXME: Tidy this up. 226 # pylint: disable=too-many-locals 227 # pylint: disable=too-many-branches 228 # pylint: disable=too-many-statements 229 # pylint: disable=too-many-nested-blocks 230 assert bui.app.classic is not None 231 if data is None: 232 bui.textwidget( 233 edit=self._loading_text, 234 text=bui.Lstr(resource='internal.unavailableNoConnectionText'), 235 ) 236 bui.spinnerwidget(edit=self._loading_spinner, visible=False) 237 else: 238 try: 239 self._loading_text.delete() 240 self._loading_spinner.delete() 241 trophystr = '' 242 try: 243 trophystr = data['trophies'] 244 num = 10 245 chunks = [ 246 trophystr[i : i + num] 247 for i in range(0, len(trophystr), num) 248 ] 249 trophystr = '\n\n'.join(chunks) 250 if trophystr == '': 251 trophystr = '-' 252 except Exception: 253 logging.exception('Error displaying trophies.') 254 account_name_spacing = 15 255 tscale = 0.65 256 ts_height = bui.get_string_height( 257 trophystr, suppress_warning=True 258 ) 259 sub_width = self._width - 80 260 sub_height = ( 261 200 262 + ts_height * tscale 263 + account_name_spacing * len(data['accountDisplayStrings']) 264 ) 265 self._subcontainer = bui.containerwidget( 266 parent=self._scrollwidget, 267 size=(sub_width, sub_height), 268 background=False, 269 ) 270 v = sub_height - 20 271 272 title_scale = 0.37 273 center = 0.3 274 maxwidth_scale = 0.45 275 showing_character = False 276 if data['profileDisplayString'] is not None: 277 tint_color = (1, 1, 1) 278 try: 279 if data['profile'] is not None: 280 profile = data['profile'] 281 assert bui.app.classic is not None 282 character = bui.app.classic.spaz_appearances.get( 283 profile['character'], None 284 ) 285 if character is not None: 286 tint_color = ( 287 profile['color'] 288 if 'color' in profile 289 else (1, 1, 1) 290 ) 291 tint2_color = ( 292 profile['highlight'] 293 if 'highlight' in profile 294 else (1, 1, 1) 295 ) 296 icon_tex = character.icon_texture 297 tint_tex = character.icon_mask_texture 298 mask_texture = bui.gettexture( 299 'characterIconMask' 300 ) 301 bui.imagewidget( 302 parent=self._subcontainer, 303 position=(sub_width * center - 40, v - 80), 304 size=(80, 80), 305 color=(1, 1, 1), 306 mask_texture=mask_texture, 307 texture=bui.gettexture(icon_tex), 308 tint_texture=bui.gettexture(tint_tex), 309 tint_color=tint_color, 310 tint2_color=tint2_color, 311 ) 312 v -= 95 313 except Exception: 314 logging.exception('Error displaying character.') 315 bui.textwidget( 316 parent=self._subcontainer, 317 size=(0, 0), 318 position=(sub_width * center, v), 319 h_align='center', 320 v_align='center', 321 scale=0.9, 322 color=bui.safecolor(tint_color, 0.7), 323 shadow=1.0, 324 text=bui.Lstr(value=data['profileDisplayString']), 325 maxwidth=sub_width * maxwidth_scale * 0.75, 326 ) 327 showing_character = True 328 v -= 33 329 330 center = 0.75 if showing_character else 0.5 331 maxwidth_scale = 0.45 if showing_character else 0.9 332 333 v = sub_height - 20 334 if len(data['accountDisplayStrings']) <= 1: 335 account_title = bui.Lstr( 336 resource='settingsWindow.accountText' 337 ) 338 else: 339 account_title = bui.Lstr( 340 resource='accountSettingsWindow.accountsText', 341 fallback_resource='settingsWindow.accountText', 342 ) 343 bui.textwidget( 344 parent=self._subcontainer, 345 size=(0, 0), 346 position=(sub_width * center, v), 347 flatness=1.0, 348 h_align='center', 349 v_align='center', 350 scale=title_scale, 351 color=bui.app.ui_v1.infotextcolor, 352 text=account_title, 353 maxwidth=sub_width * maxwidth_scale, 354 ) 355 draw_small = ( 356 showing_character or len(data['accountDisplayStrings']) > 1 357 ) 358 v -= 14 if draw_small else 20 359 for account_string in data['accountDisplayStrings']: 360 bui.textwidget( 361 parent=self._subcontainer, 362 size=(0, 0), 363 position=(sub_width * center, v), 364 h_align='center', 365 v_align='center', 366 scale=0.55 if draw_small else 0.8, 367 text=account_string, 368 maxwidth=sub_width * maxwidth_scale, 369 ) 370 v -= account_name_spacing 371 372 v += account_name_spacing 373 v -= 25 if showing_character else 29 374 375 bui.textwidget( 376 parent=self._subcontainer, 377 size=(0, 0), 378 position=(sub_width * center, v), 379 flatness=1.0, 380 h_align='center', 381 v_align='center', 382 scale=title_scale, 383 color=bui.app.ui_v1.infotextcolor, 384 text=bui.Lstr(resource='rankText'), 385 maxwidth=sub_width * maxwidth_scale, 386 ) 387 v -= 14 388 if data['rank'] is None: 389 rank_str = '-' 390 suffix_offset = None 391 else: 392 str_raw = bui.Lstr( 393 resource='league.rankInLeagueText' 394 ).evaluate() 395 # FIXME: Would be nice to not have to eval this. 396 rank_str = bui.Lstr( 397 resource='league.rankInLeagueText', 398 subs=[ 399 ('${RANK}', str(data['rank'][2])), 400 ( 401 '${NAME}', 402 bui.Lstr( 403 translate=('leagueNames', data['rank'][0]) 404 ), 405 ), 406 ('${SUFFIX}', ''), 407 ], 408 ).evaluate() 409 rank_str_width = min( 410 sub_width * maxwidth_scale, 411 bui.get_string_width(rank_str, suppress_warning=True) 412 * 0.55, 413 ) 414 415 # Only tack our suffix on if its at the end and only for 416 # non-diamond leagues. 417 if ( 418 str_raw.endswith('${SUFFIX}') 419 and data['rank'][0] != 'Diamond' 420 ): 421 suffix_offset = rank_str_width * 0.5 + 2 422 else: 423 suffix_offset = None 424 425 bui.textwidget( 426 parent=self._subcontainer, 427 size=(0, 0), 428 position=(sub_width * center, v), 429 h_align='center', 430 v_align='center', 431 scale=0.55, 432 text=rank_str, 433 maxwidth=sub_width * maxwidth_scale, 434 ) 435 if suffix_offset is not None: 436 assert data['rank'] is not None 437 bui.textwidget( 438 parent=self._subcontainer, 439 size=(0, 0), 440 position=(sub_width * center + suffix_offset, v + 3), 441 h_align='left', 442 v_align='center', 443 scale=0.29, 444 flatness=1.0, 445 text='[' + str(data['rank'][1]) + ']', 446 ) 447 v -= 14 448 449 str_raw = bui.Lstr( 450 resource='league.rankInLeagueText' 451 ).evaluate() 452 old_offs = -50 453 prev_ranks_shown = 0 454 for prev_rank in data['prevRanks']: 455 rank_str = bui.Lstr( 456 value='${S}: ${I}', 457 subs=[ 458 ( 459 '${S}', 460 bui.Lstr( 461 resource='league.seasonText', 462 subs=[('${NUMBER}', str(prev_rank[0]))], 463 ), 464 ), 465 ( 466 '${I}', 467 bui.Lstr( 468 resource='league.rankInLeagueText', 469 subs=[ 470 ('${RANK}', str(prev_rank[3])), 471 ( 472 '${NAME}', 473 bui.Lstr( 474 translate=( 475 'leagueNames', 476 prev_rank[1], 477 ) 478 ), 479 ), 480 ('${SUFFIX}', ''), 481 ], 482 ), 483 ), 484 ], 485 ).evaluate() 486 rank_str_width = min( 487 sub_width * maxwidth_scale, 488 bui.get_string_width(rank_str, suppress_warning=True) 489 * 0.3, 490 ) 491 492 # Only tack our suffix on if its at the end and only for 493 # non-diamond leagues. 494 if ( 495 str_raw.endswith('${SUFFIX}') 496 and prev_rank[1] != 'Diamond' 497 ): 498 suffix_offset = rank_str_width + 2 499 else: 500 suffix_offset = None 501 bui.textwidget( 502 parent=self._subcontainer, 503 size=(0, 0), 504 position=(sub_width * center + old_offs, v), 505 h_align='left', 506 v_align='center', 507 scale=0.3, 508 text=rank_str, 509 flatness=1.0, 510 maxwidth=sub_width * maxwidth_scale, 511 ) 512 if suffix_offset is not None: 513 bui.textwidget( 514 parent=self._subcontainer, 515 size=(0, 0), 516 position=( 517 sub_width * center + old_offs + suffix_offset, 518 v + 1, 519 ), 520 h_align='left', 521 v_align='center', 522 scale=0.20, 523 flatness=1.0, 524 text='[' + str(prev_rank[2]) + ']', 525 ) 526 prev_ranks_shown += 1 527 v -= 10 528 529 v -= 13 530 531 bui.textwidget( 532 parent=self._subcontainer, 533 size=(0, 0), 534 position=(sub_width * center, v), 535 flatness=1.0, 536 h_align='center', 537 v_align='center', 538 scale=title_scale, 539 color=bui.app.ui_v1.infotextcolor, 540 text=bui.Lstr(resource='achievementsText'), 541 maxwidth=sub_width * maxwidth_scale, 542 ) 543 v -= 14 544 bui.textwidget( 545 parent=self._subcontainer, 546 size=(0, 0), 547 position=(sub_width * center, v), 548 h_align='center', 549 v_align='center', 550 scale=0.55, 551 text=str(data['achievementsCompleted']) 552 + ' / ' 553 + str(len(bui.app.classic.ach.achievements)), 554 maxwidth=sub_width * maxwidth_scale, 555 ) 556 v -= 25 557 558 if prev_ranks_shown == 0 and showing_character: 559 v -= 20 560 elif prev_ranks_shown == 1 and showing_character: 561 v -= 10 562 563 center = 0.5 564 maxwidth_scale = 0.9 565 566 bui.textwidget( 567 parent=self._subcontainer, 568 size=(0, 0), 569 position=(sub_width * center, v), 570 h_align='center', 571 v_align='center', 572 scale=title_scale, 573 color=bui.app.ui_v1.infotextcolor, 574 flatness=1.0, 575 text=bui.Lstr( 576 resource='trophiesThisSeasonText', 577 fallback_resource='trophiesText', 578 ), 579 maxwidth=sub_width * maxwidth_scale, 580 ) 581 v -= 19 582 bui.textwidget( 583 parent=self._subcontainer, 584 size=(0, ts_height), 585 position=(sub_width * 0.5, v - ts_height * tscale), 586 h_align='center', 587 v_align='top', 588 corner_scale=tscale, 589 text=trophystr, 590 ) 591 592 except Exception: 593 logging.exception('Error displaying account info.') 594 595 def _on_cancel_press(self) -> None: 596 self._transition_out() 597 598 def _transition_out(self) -> None: 599 if not self._transitioning_out: 600 self._transitioning_out = True 601 bui.containerwidget(edit=self.root_widget, transition='out_scale') 602 603 @override 604 def on_popup_cancel(self) -> None: 605 bui.getsound('swish').play() 606 self._transition_out()
21class AccountViewerWindow(PopupWindow): 22 """Popup window that displays info for an account.""" 23 24 def __init__( 25 self, 26 account_id: str, 27 *, 28 profile_id: str | None = None, 29 position: tuple[float, float] = (0.0, 0.0), 30 scale: float | None = None, 31 offset: tuple[float, float] = (0.0, 0.0), 32 ): 33 if bui.app.classic is None: 34 raise RuntimeError('This requires classic support.') 35 36 plus = bui.app.plus 37 assert plus is not None 38 39 self._account_id = account_id 40 self._profile_id = profile_id 41 42 uiscale = bui.app.ui_v1.uiscale 43 if scale is None: 44 scale = ( 45 2.6 46 if uiscale is bui.UIScale.SMALL 47 else 1.8 if uiscale is bui.UIScale.MEDIUM else 1.4 48 ) 49 self._transitioning_out = False 50 51 self._width = 400 52 self._height = ( 53 300 54 if uiscale is bui.UIScale.SMALL 55 else 400 if uiscale is bui.UIScale.MEDIUM else 450 56 ) 57 self._subcontainer: bui.Widget | None = None 58 59 bg_color = (0.5, 0.4, 0.6) 60 61 # Creates our _root_widget. 62 super().__init__( 63 position=position, 64 size=(self._width, self._height), 65 scale=scale, 66 bg_color=bg_color, 67 offset=offset, 68 ) 69 70 self._cancel_button = bui.buttonwidget( 71 parent=self.root_widget, 72 position=(50, self._height - 30), 73 size=(50, 50), 74 scale=0.5, 75 label='', 76 color=bg_color, 77 on_activate_call=self._on_cancel_press, 78 autoselect=True, 79 icon=bui.gettexture('crossOut'), 80 iconscale=1.2, 81 ) 82 83 self._title_text = bui.textwidget( 84 parent=self.root_widget, 85 position=(self._width * 0.5, self._height - 20), 86 size=(0, 0), 87 h_align='center', 88 v_align='center', 89 scale=0.6, 90 text=bui.Lstr(resource='playerInfoText'), 91 maxwidth=200, 92 color=bui.app.ui_v1.title_color, 93 ) 94 95 self._scrollwidget = bui.scrollwidget( 96 parent=self.root_widget, 97 size=(self._width - 60, self._height - 70), 98 position=(30, 30), 99 capture_arrows=True, 100 simple_culling_v=10, 101 border_opacity=0.4, 102 ) 103 bui.widget(edit=self._scrollwidget, autoselect=True) 104 105 # Note to self: Make sure to always update loading text and 106 # spinner visibility together. 107 self._loading_text = bui.textwidget( 108 parent=self._scrollwidget, 109 scale=0.5, 110 text='', 111 size=(self._width - 60, 100), 112 h_align='center', 113 v_align='center', 114 ) 115 self._loading_spinner = bui.spinnerwidget( 116 parent=self.root_widget, 117 position=(self._width * 0.5, self._height * 0.5), 118 style='bomb', 119 size=48, 120 ) 121 122 # In cases where the user most likely has a browser/email, lets 123 # offer a 'report this user' button. 124 if ( 125 bui.is_browser_likely_available() 126 and plus.get_v1_account_misc_read_val( 127 'showAccountExtrasMenu', False 128 ) 129 ): 130 self._extras_menu_button = bui.buttonwidget( 131 parent=self.root_widget, 132 size=(20, 20), 133 position=(self._width - 60, self._height - 30), 134 autoselect=True, 135 label='...', 136 button_type='square', 137 color=(0.64, 0.52, 0.69), 138 textcolor=(0.57, 0.47, 0.57), 139 on_activate_call=self._on_extras_menu_press, 140 ) 141 142 bui.containerwidget( 143 edit=self.root_widget, cancel_button=self._cancel_button 144 ) 145 146 bui.app.classic.master_server_v1_get( 147 'bsAccountInfo', 148 { 149 'buildNumber': bui.app.env.engine_build_number, 150 'accountID': self._account_id, 151 'profileID': self._profile_id, 152 }, 153 callback=bui.WeakCall(self._on_query_response), 154 ) 155 156 def popup_menu_selected_choice( 157 self, window: PopupMenu, choice: str 158 ) -> None: 159 """Called when a menu entry is selected.""" 160 del window # Unused arg. 161 if choice == 'more': 162 self._on_more_press() 163 elif choice == 'report': 164 self._on_report_press() 165 elif choice == 'ban': 166 self._on_ban_press() 167 else: 168 print('ERROR: unknown account info extras menu item:', choice) 169 170 def popup_menu_closing(self, window: PopupMenu) -> None: 171 """Called when the popup menu is closing.""" 172 173 def _on_extras_menu_press(self) -> None: 174 choices = ['more', 'report'] 175 choices_display = [ 176 bui.Lstr(resource='coopSelectWindow.seeMoreText'), 177 bui.Lstr(resource='reportThisPlayerText'), 178 ] 179 is_admin = False 180 if is_admin: 181 bui.screenmessage('TEMP FORCING ADMIN ON') 182 choices.append('ban') 183 choices_display.append(bui.Lstr(resource='banThisPlayerText')) 184 185 assert bui.app.classic is not None 186 uiscale = bui.app.ui_v1.uiscale 187 PopupMenuWindow( 188 position=self._extras_menu_button.get_screen_space_center(), 189 scale=( 190 2.3 191 if uiscale is bui.UIScale.SMALL 192 else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 193 ), 194 choices=choices, 195 choices_display=choices_display, 196 current_choice='more', 197 delegate=self, 198 ) 199 200 def _on_ban_press(self) -> None: 201 plus = bui.app.plus 202 assert plus is not None 203 204 plus.add_v1_account_transaction( 205 {'type': 'BAN_ACCOUNT', 'account': self._account_id} 206 ) 207 plus.run_v1_account_transactions() 208 209 def _on_report_press(self) -> None: 210 from bauiv1lib import report 211 212 report.ReportPlayerWindow( 213 self._account_id, origin_widget=self._extras_menu_button 214 ) 215 216 def _on_more_press(self) -> None: 217 plus = bui.app.plus 218 assert plus is not None 219 bui.open_url( 220 plus.get_master_server_address() 221 + '/highscores?profile=' 222 + self._account_id 223 ) 224 225 def _on_query_response(self, data: dict[str, Any] | None) -> None: 226 # FIXME: Tidy this up. 227 # pylint: disable=too-many-locals 228 # pylint: disable=too-many-branches 229 # pylint: disable=too-many-statements 230 # pylint: disable=too-many-nested-blocks 231 assert bui.app.classic is not None 232 if data is None: 233 bui.textwidget( 234 edit=self._loading_text, 235 text=bui.Lstr(resource='internal.unavailableNoConnectionText'), 236 ) 237 bui.spinnerwidget(edit=self._loading_spinner, visible=False) 238 else: 239 try: 240 self._loading_text.delete() 241 self._loading_spinner.delete() 242 trophystr = '' 243 try: 244 trophystr = data['trophies'] 245 num = 10 246 chunks = [ 247 trophystr[i : i + num] 248 for i in range(0, len(trophystr), num) 249 ] 250 trophystr = '\n\n'.join(chunks) 251 if trophystr == '': 252 trophystr = '-' 253 except Exception: 254 logging.exception('Error displaying trophies.') 255 account_name_spacing = 15 256 tscale = 0.65 257 ts_height = bui.get_string_height( 258 trophystr, suppress_warning=True 259 ) 260 sub_width = self._width - 80 261 sub_height = ( 262 200 263 + ts_height * tscale 264 + account_name_spacing * len(data['accountDisplayStrings']) 265 ) 266 self._subcontainer = bui.containerwidget( 267 parent=self._scrollwidget, 268 size=(sub_width, sub_height), 269 background=False, 270 ) 271 v = sub_height - 20 272 273 title_scale = 0.37 274 center = 0.3 275 maxwidth_scale = 0.45 276 showing_character = False 277 if data['profileDisplayString'] is not None: 278 tint_color = (1, 1, 1) 279 try: 280 if data['profile'] is not None: 281 profile = data['profile'] 282 assert bui.app.classic is not None 283 character = bui.app.classic.spaz_appearances.get( 284 profile['character'], None 285 ) 286 if character is not None: 287 tint_color = ( 288 profile['color'] 289 if 'color' in profile 290 else (1, 1, 1) 291 ) 292 tint2_color = ( 293 profile['highlight'] 294 if 'highlight' in profile 295 else (1, 1, 1) 296 ) 297 icon_tex = character.icon_texture 298 tint_tex = character.icon_mask_texture 299 mask_texture = bui.gettexture( 300 'characterIconMask' 301 ) 302 bui.imagewidget( 303 parent=self._subcontainer, 304 position=(sub_width * center - 40, v - 80), 305 size=(80, 80), 306 color=(1, 1, 1), 307 mask_texture=mask_texture, 308 texture=bui.gettexture(icon_tex), 309 tint_texture=bui.gettexture(tint_tex), 310 tint_color=tint_color, 311 tint2_color=tint2_color, 312 ) 313 v -= 95 314 except Exception: 315 logging.exception('Error displaying character.') 316 bui.textwidget( 317 parent=self._subcontainer, 318 size=(0, 0), 319 position=(sub_width * center, v), 320 h_align='center', 321 v_align='center', 322 scale=0.9, 323 color=bui.safecolor(tint_color, 0.7), 324 shadow=1.0, 325 text=bui.Lstr(value=data['profileDisplayString']), 326 maxwidth=sub_width * maxwidth_scale * 0.75, 327 ) 328 showing_character = True 329 v -= 33 330 331 center = 0.75 if showing_character else 0.5 332 maxwidth_scale = 0.45 if showing_character else 0.9 333 334 v = sub_height - 20 335 if len(data['accountDisplayStrings']) <= 1: 336 account_title = bui.Lstr( 337 resource='settingsWindow.accountText' 338 ) 339 else: 340 account_title = bui.Lstr( 341 resource='accountSettingsWindow.accountsText', 342 fallback_resource='settingsWindow.accountText', 343 ) 344 bui.textwidget( 345 parent=self._subcontainer, 346 size=(0, 0), 347 position=(sub_width * center, v), 348 flatness=1.0, 349 h_align='center', 350 v_align='center', 351 scale=title_scale, 352 color=bui.app.ui_v1.infotextcolor, 353 text=account_title, 354 maxwidth=sub_width * maxwidth_scale, 355 ) 356 draw_small = ( 357 showing_character or len(data['accountDisplayStrings']) > 1 358 ) 359 v -= 14 if draw_small else 20 360 for account_string in data['accountDisplayStrings']: 361 bui.textwidget( 362 parent=self._subcontainer, 363 size=(0, 0), 364 position=(sub_width * center, v), 365 h_align='center', 366 v_align='center', 367 scale=0.55 if draw_small else 0.8, 368 text=account_string, 369 maxwidth=sub_width * maxwidth_scale, 370 ) 371 v -= account_name_spacing 372 373 v += account_name_spacing 374 v -= 25 if showing_character else 29 375 376 bui.textwidget( 377 parent=self._subcontainer, 378 size=(0, 0), 379 position=(sub_width * center, v), 380 flatness=1.0, 381 h_align='center', 382 v_align='center', 383 scale=title_scale, 384 color=bui.app.ui_v1.infotextcolor, 385 text=bui.Lstr(resource='rankText'), 386 maxwidth=sub_width * maxwidth_scale, 387 ) 388 v -= 14 389 if data['rank'] is None: 390 rank_str = '-' 391 suffix_offset = None 392 else: 393 str_raw = bui.Lstr( 394 resource='league.rankInLeagueText' 395 ).evaluate() 396 # FIXME: Would be nice to not have to eval this. 397 rank_str = bui.Lstr( 398 resource='league.rankInLeagueText', 399 subs=[ 400 ('${RANK}', str(data['rank'][2])), 401 ( 402 '${NAME}', 403 bui.Lstr( 404 translate=('leagueNames', data['rank'][0]) 405 ), 406 ), 407 ('${SUFFIX}', ''), 408 ], 409 ).evaluate() 410 rank_str_width = min( 411 sub_width * maxwidth_scale, 412 bui.get_string_width(rank_str, suppress_warning=True) 413 * 0.55, 414 ) 415 416 # Only tack our suffix on if its at the end and only for 417 # non-diamond leagues. 418 if ( 419 str_raw.endswith('${SUFFIX}') 420 and data['rank'][0] != 'Diamond' 421 ): 422 suffix_offset = rank_str_width * 0.5 + 2 423 else: 424 suffix_offset = None 425 426 bui.textwidget( 427 parent=self._subcontainer, 428 size=(0, 0), 429 position=(sub_width * center, v), 430 h_align='center', 431 v_align='center', 432 scale=0.55, 433 text=rank_str, 434 maxwidth=sub_width * maxwidth_scale, 435 ) 436 if suffix_offset is not None: 437 assert data['rank'] is not None 438 bui.textwidget( 439 parent=self._subcontainer, 440 size=(0, 0), 441 position=(sub_width * center + suffix_offset, v + 3), 442 h_align='left', 443 v_align='center', 444 scale=0.29, 445 flatness=1.0, 446 text='[' + str(data['rank'][1]) + ']', 447 ) 448 v -= 14 449 450 str_raw = bui.Lstr( 451 resource='league.rankInLeagueText' 452 ).evaluate() 453 old_offs = -50 454 prev_ranks_shown = 0 455 for prev_rank in data['prevRanks']: 456 rank_str = bui.Lstr( 457 value='${S}: ${I}', 458 subs=[ 459 ( 460 '${S}', 461 bui.Lstr( 462 resource='league.seasonText', 463 subs=[('${NUMBER}', str(prev_rank[0]))], 464 ), 465 ), 466 ( 467 '${I}', 468 bui.Lstr( 469 resource='league.rankInLeagueText', 470 subs=[ 471 ('${RANK}', str(prev_rank[3])), 472 ( 473 '${NAME}', 474 bui.Lstr( 475 translate=( 476 'leagueNames', 477 prev_rank[1], 478 ) 479 ), 480 ), 481 ('${SUFFIX}', ''), 482 ], 483 ), 484 ), 485 ], 486 ).evaluate() 487 rank_str_width = min( 488 sub_width * maxwidth_scale, 489 bui.get_string_width(rank_str, suppress_warning=True) 490 * 0.3, 491 ) 492 493 # Only tack our suffix on if its at the end and only for 494 # non-diamond leagues. 495 if ( 496 str_raw.endswith('${SUFFIX}') 497 and prev_rank[1] != 'Diamond' 498 ): 499 suffix_offset = rank_str_width + 2 500 else: 501 suffix_offset = None 502 bui.textwidget( 503 parent=self._subcontainer, 504 size=(0, 0), 505 position=(sub_width * center + old_offs, v), 506 h_align='left', 507 v_align='center', 508 scale=0.3, 509 text=rank_str, 510 flatness=1.0, 511 maxwidth=sub_width * maxwidth_scale, 512 ) 513 if suffix_offset is not None: 514 bui.textwidget( 515 parent=self._subcontainer, 516 size=(0, 0), 517 position=( 518 sub_width * center + old_offs + suffix_offset, 519 v + 1, 520 ), 521 h_align='left', 522 v_align='center', 523 scale=0.20, 524 flatness=1.0, 525 text='[' + str(prev_rank[2]) + ']', 526 ) 527 prev_ranks_shown += 1 528 v -= 10 529 530 v -= 13 531 532 bui.textwidget( 533 parent=self._subcontainer, 534 size=(0, 0), 535 position=(sub_width * center, v), 536 flatness=1.0, 537 h_align='center', 538 v_align='center', 539 scale=title_scale, 540 color=bui.app.ui_v1.infotextcolor, 541 text=bui.Lstr(resource='achievementsText'), 542 maxwidth=sub_width * maxwidth_scale, 543 ) 544 v -= 14 545 bui.textwidget( 546 parent=self._subcontainer, 547 size=(0, 0), 548 position=(sub_width * center, v), 549 h_align='center', 550 v_align='center', 551 scale=0.55, 552 text=str(data['achievementsCompleted']) 553 + ' / ' 554 + str(len(bui.app.classic.ach.achievements)), 555 maxwidth=sub_width * maxwidth_scale, 556 ) 557 v -= 25 558 559 if prev_ranks_shown == 0 and showing_character: 560 v -= 20 561 elif prev_ranks_shown == 1 and showing_character: 562 v -= 10 563 564 center = 0.5 565 maxwidth_scale = 0.9 566 567 bui.textwidget( 568 parent=self._subcontainer, 569 size=(0, 0), 570 position=(sub_width * center, v), 571 h_align='center', 572 v_align='center', 573 scale=title_scale, 574 color=bui.app.ui_v1.infotextcolor, 575 flatness=1.0, 576 text=bui.Lstr( 577 resource='trophiesThisSeasonText', 578 fallback_resource='trophiesText', 579 ), 580 maxwidth=sub_width * maxwidth_scale, 581 ) 582 v -= 19 583 bui.textwidget( 584 parent=self._subcontainer, 585 size=(0, ts_height), 586 position=(sub_width * 0.5, v - ts_height * tscale), 587 h_align='center', 588 v_align='top', 589 corner_scale=tscale, 590 text=trophystr, 591 ) 592 593 except Exception: 594 logging.exception('Error displaying account info.') 595 596 def _on_cancel_press(self) -> None: 597 self._transition_out() 598 599 def _transition_out(self) -> None: 600 if not self._transitioning_out: 601 self._transitioning_out = True 602 bui.containerwidget(edit=self.root_widget, transition='out_scale') 603 604 @override 605 def on_popup_cancel(self) -> None: 606 bui.getsound('swish').play() 607 self._transition_out()
Popup window that displays info for an account.
AccountViewerWindow( account_id: str, *, profile_id: str | None = None, position: tuple[float, float] = (0.0, 0.0), scale: float | None = None, offset: tuple[float, float] = (0.0, 0.0))
24 def __init__( 25 self, 26 account_id: str, 27 *, 28 profile_id: str | None = None, 29 position: tuple[float, float] = (0.0, 0.0), 30 scale: float | None = None, 31 offset: tuple[float, float] = (0.0, 0.0), 32 ): 33 if bui.app.classic is None: 34 raise RuntimeError('This requires classic support.') 35 36 plus = bui.app.plus 37 assert plus is not None 38 39 self._account_id = account_id 40 self._profile_id = profile_id 41 42 uiscale = bui.app.ui_v1.uiscale 43 if scale is None: 44 scale = ( 45 2.6 46 if uiscale is bui.UIScale.SMALL 47 else 1.8 if uiscale is bui.UIScale.MEDIUM else 1.4 48 ) 49 self._transitioning_out = False 50 51 self._width = 400 52 self._height = ( 53 300 54 if uiscale is bui.UIScale.SMALL 55 else 400 if uiscale is bui.UIScale.MEDIUM else 450 56 ) 57 self._subcontainer: bui.Widget | None = None 58 59 bg_color = (0.5, 0.4, 0.6) 60 61 # Creates our _root_widget. 62 super().__init__( 63 position=position, 64 size=(self._width, self._height), 65 scale=scale, 66 bg_color=bg_color, 67 offset=offset, 68 ) 69 70 self._cancel_button = bui.buttonwidget( 71 parent=self.root_widget, 72 position=(50, self._height - 30), 73 size=(50, 50), 74 scale=0.5, 75 label='', 76 color=bg_color, 77 on_activate_call=self._on_cancel_press, 78 autoselect=True, 79 icon=bui.gettexture('crossOut'), 80 iconscale=1.2, 81 ) 82 83 self._title_text = bui.textwidget( 84 parent=self.root_widget, 85 position=(self._width * 0.5, self._height - 20), 86 size=(0, 0), 87 h_align='center', 88 v_align='center', 89 scale=0.6, 90 text=bui.Lstr(resource='playerInfoText'), 91 maxwidth=200, 92 color=bui.app.ui_v1.title_color, 93 ) 94 95 self._scrollwidget = bui.scrollwidget( 96 parent=self.root_widget, 97 size=(self._width - 60, self._height - 70), 98 position=(30, 30), 99 capture_arrows=True, 100 simple_culling_v=10, 101 border_opacity=0.4, 102 ) 103 bui.widget(edit=self._scrollwidget, autoselect=True) 104 105 # Note to self: Make sure to always update loading text and 106 # spinner visibility together. 107 self._loading_text = bui.textwidget( 108 parent=self._scrollwidget, 109 scale=0.5, 110 text='', 111 size=(self._width - 60, 100), 112 h_align='center', 113 v_align='center', 114 ) 115 self._loading_spinner = bui.spinnerwidget( 116 parent=self.root_widget, 117 position=(self._width * 0.5, self._height * 0.5), 118 style='bomb', 119 size=48, 120 ) 121 122 # In cases where the user most likely has a browser/email, lets 123 # offer a 'report this user' button. 124 if ( 125 bui.is_browser_likely_available() 126 and plus.get_v1_account_misc_read_val( 127 'showAccountExtrasMenu', False 128 ) 129 ): 130 self._extras_menu_button = bui.buttonwidget( 131 parent=self.root_widget, 132 size=(20, 20), 133 position=(self._width - 60, self._height - 30), 134 autoselect=True, 135 label='...', 136 button_type='square', 137 color=(0.64, 0.52, 0.69), 138 textcolor=(0.57, 0.47, 0.57), 139 on_activate_call=self._on_extras_menu_press, 140 ) 141 142 bui.containerwidget( 143 edit=self.root_widget, cancel_button=self._cancel_button 144 ) 145 146 bui.app.classic.master_server_v1_get( 147 'bsAccountInfo', 148 { 149 'buildNumber': bui.app.env.engine_build_number, 150 'accountID': self._account_id, 151 'profileID': self._profile_id, 152 }, 153 callback=bui.WeakCall(self._on_query_response), 154 )
@override
def
on_popup_cancel(self) -> None:
604 @override 605 def on_popup_cancel(self) -> None: 606 bui.getsound('swish').play() 607 self._transition_out()
Called when the popup is canceled.
Cancels can occur due to clicking outside the window, hitting escape, etc.