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