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()
class AccountViewerWindow(bastd.ui.popup.PopupWindow):
 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        )
def popup_menu_selected_choice(self, window: bastd.ui.popup.PopupMenu, choice: str) -> None:
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)

Called when a menu entry is selected.

def popup_menu_closing(self, window: bastd.ui.popup.PopupMenu) -> None:
162    def popup_menu_closing(self, window: popup.PopupMenu) -> None:
163        """Called when the popup menu is closing."""

Called when the popup menu is closing.

def on_popup_cancel(self) -> None:
590    def on_popup_cancel(self) -> None:
591        ba.playsound(ba.getsound('swish'))
592        self._transition_out()

Called when the popup is canceled.

Cancels can occur due to clicking outside the window, hitting escape, etc.