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

Called when the popup menu is closing.

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

Called when the popup is canceled.

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