bastd.ui.account.viewer

Provides a popup for displaying info about any account.

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

Popup window that displays info for an account.

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

Called when a menu entry is selected.

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

Called when the popup menu is closing.

def on_popup_cancel(self) -> None:
474    def on_popup_cancel(self) -> None:
475        ba.playsound(ba.getsound('swish'))
476        self._transition_out()

Called when the popup is canceled.

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