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

Called when the popup menu is closing.

@override
def on_popup_cancel(self) -> None:
604    @override
605    def on_popup_cancel(self) -> None:
606        bui.getsound('swish').play()
607        self._transition_out()

Called when the popup is canceled.

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