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