bauiv1lib.account.viewer

Provides a popup for displaying info about any account.

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

Popup window that displays info for an account.

AccountViewerWindow( account_id: str, profile_id: str | None = None, position: tuple[float, float] = (0.0, 0.0), scale: float | None = None, offset: tuple[float, float] = (0.0, 0.0))
 24    def __init__(
 25        self,
 26        account_id: str,
 27        profile_id: str | None = None,
 28        position: tuple[float, float] = (0.0, 0.0),
 29        scale: float | None = None,
 30        offset: tuple[float, float] = (0.0, 0.0),
 31    ):
 32        if bui.app.classic is None:
 33            raise RuntimeError('This requires classic support.')
 34
 35        plus = bui.app.plus
 36        assert plus is not None
 37
 38        self._account_id = account_id
 39        self._profile_id = profile_id
 40
 41        uiscale = bui.app.ui_v1.uiscale
 42        if scale is None:
 43            scale = (
 44                2.6
 45                if uiscale is bui.UIScale.SMALL
 46                else 1.8 if uiscale is bui.UIScale.MEDIUM else 1.4
 47            )
 48        self._transitioning_out = False
 49
 50        self._width = 400
 51        self._height = (
 52            300
 53            if uiscale is bui.UIScale.SMALL
 54            else 400 if uiscale is bui.UIScale.MEDIUM else 450
 55        )
 56        self._subcontainer: bui.Widget | None = None
 57
 58        bg_color = (0.5, 0.4, 0.6)
 59
 60        # Creates our _root_widget.
 61        super().__init__(
 62            position=position,
 63            size=(self._width, self._height),
 64            scale=scale,
 65            bg_color=bg_color,
 66            offset=offset,
 67        )
 68
 69        self._cancel_button = bui.buttonwidget(
 70            parent=self.root_widget,
 71            position=(50, self._height - 30),
 72            size=(50, 50),
 73            scale=0.5,
 74            label='',
 75            color=bg_color,
 76            on_activate_call=self._on_cancel_press,
 77            autoselect=True,
 78            icon=bui.gettexture('crossOut'),
 79            iconscale=1.2,
 80        )
 81
 82        self._title_text = bui.textwidget(
 83            parent=self.root_widget,
 84            position=(self._width * 0.5, self._height - 20),
 85            size=(0, 0),
 86            h_align='center',
 87            v_align='center',
 88            scale=0.6,
 89            text=bui.Lstr(resource='playerInfoText'),
 90            maxwidth=200,
 91            color=(0.7, 0.7, 0.7, 0.7),
 92        )
 93
 94        self._scrollwidget = bui.scrollwidget(
 95            parent=self.root_widget,
 96            size=(self._width - 60, self._height - 70),
 97            position=(30, 30),
 98            capture_arrows=True,
 99            simple_culling_v=10,
100        )
101        bui.widget(edit=self._scrollwidget, autoselect=True)
102
103        self._loading_text = bui.textwidget(
104            parent=self._scrollwidget,
105            scale=0.5,
106            text=bui.Lstr(
107                value='${A}...',
108                subs=[('${A}', bui.Lstr(resource='loadingText'))],
109            ),
110            size=(self._width - 60, 100),
111            h_align='center',
112            v_align='center',
113        )
114
115        # In cases where the user most likely has a browser/email, lets
116        # offer a 'report this user' button.
117        if (
118            bui.is_browser_likely_available()
119            and plus.get_v1_account_misc_read_val(
120                'showAccountExtrasMenu', False
121            )
122        ):
123            self._extras_menu_button = bui.buttonwidget(
124                parent=self.root_widget,
125                size=(20, 20),
126                position=(self._width - 60, self._height - 30),
127                autoselect=True,
128                label='...',
129                button_type='square',
130                color=(0.64, 0.52, 0.69),
131                textcolor=(0.57, 0.47, 0.57),
132                on_activate_call=self._on_extras_menu_press,
133            )
134
135        bui.containerwidget(
136            edit=self.root_widget, cancel_button=self._cancel_button
137        )
138
139        bui.app.classic.master_server_v1_get(
140            'bsAccountInfo',
141            {
142                'buildNumber': bui.app.env.build_number,
143                'accountID': self._account_id,
144                'profileID': self._profile_id,
145            },
146            callback=bui.WeakCall(self._on_query_response),
147        )
def popup_menu_selected_choice(self, window: bauiv1lib.popup.PopupMenu, choice: str) -> None:
149    def popup_menu_selected_choice(
150        self, window: PopupMenu, choice: str
151    ) -> None:
152        """Called when a menu entry is selected."""
153        del window  # Unused arg.
154        if choice == 'more':
155            self._on_more_press()
156        elif choice == 'report':
157            self._on_report_press()
158        elif choice == 'ban':
159            self._on_ban_press()
160        else:
161            print('ERROR: unknown account info extras menu item:', choice)

Called when a menu entry is selected.

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

Called when the popup menu is closing.

@override
def on_popup_cancel(self) -> None:
595    @override
596    def on_popup_cancel(self) -> None:
597        bui.getsound('swish').play()
598        self._transition_out()

Called when the popup is canceled.

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