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