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