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

Called when a menu entry is selected.

def popup_menu_closing(self, window: bauiv1lib.popup.PopupMenu) -> None:
166    def popup_menu_closing(self, window: PopupMenu) -> None:
167        """Called when the popup menu is closing."""

Called when the popup menu is closing.

def on_popup_cancel(self) -> None:
600    def on_popup_cancel(self) -> None:
601        bui.getsound('swish').play()
602        self._transition_out()

Called when the popup is canceled.

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