bastd.ui.profile.edit

Provides UI to edit a player profile.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides UI to edit a player profile."""
  4
  5from __future__ import annotations
  6
  7import random
  8from typing import TYPE_CHECKING, cast
  9
 10import _ba
 11import ba
 12
 13if TYPE_CHECKING:
 14    from bastd.ui.colorpicker import ColorPicker
 15
 16
 17class EditProfileWindow(ba.Window):
 18    """Window for editing a player profile."""
 19
 20    # FIXME: WILL NEED TO CHANGE THIS FOR UILOCATION.
 21    def reload_window(self) -> None:
 22        """Transitions out and recreates ourself."""
 23        ba.containerwidget(edit=self._root_widget, transition='out_left')
 24        ba.app.ui.set_main_menu_window(
 25            EditProfileWindow(self.getname(),
 26                              self._in_main_menu).get_root_widget())
 27
 28    def __init__(self,
 29                 existing_profile: str | None,
 30                 in_main_menu: bool,
 31                 transition: str = 'in_right'):
 32        # FIXME: Tidy this up a bit.
 33        # pylint: disable=too-many-branches
 34        # pylint: disable=too-many-statements
 35        # pylint: disable=too-many-locals
 36        from ba.internal import get_player_profile_colors
 37        self._in_main_menu = in_main_menu
 38        self._existing_profile = existing_profile
 39        self._r = 'editProfileWindow'
 40        self._spazzes: list[str] = []
 41        self._icon_textures: list[ba.Texture] = []
 42        self._icon_tint_textures: list[ba.Texture] = []
 43
 44        # Grab profile colors or pick random ones.
 45        self._color, self._highlight = get_player_profile_colors(
 46            existing_profile)
 47        uiscale = ba.app.ui.uiscale
 48        self._width = width = 780.0 if uiscale is ba.UIScale.SMALL else 680.0
 49        self._x_inset = x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0
 50        self._height = height = (
 51            350.0 if uiscale is ba.UIScale.SMALL else
 52            400.0 if uiscale is ba.UIScale.MEDIUM else 450.0)
 53        spacing = 40
 54        self._base_scale = (2.05 if uiscale is ba.UIScale.SMALL else
 55                            1.5 if uiscale is ba.UIScale.MEDIUM else 1.0)
 56        top_extra = 15 if uiscale is ba.UIScale.SMALL else 15
 57        super().__init__(root_widget=ba.containerwidget(
 58            size=(width, height + top_extra),
 59            transition=transition,
 60            scale=self._base_scale,
 61            stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0)))
 62        cancel_button = btn = ba.buttonwidget(
 63            parent=self._root_widget,
 64            position=(52 + x_inset, height - 60),
 65            size=(155, 60),
 66            scale=0.8,
 67            autoselect=True,
 68            label=ba.Lstr(resource='cancelText'),
 69            on_activate_call=self._cancel)
 70        ba.containerwidget(edit=self._root_widget, cancel_button=btn)
 71        save_button = btn = ba.buttonwidget(parent=self._root_widget,
 72                                            position=(width - (177 + x_inset),
 73                                                      height - 60),
 74                                            size=(155, 60),
 75                                            autoselect=True,
 76                                            scale=0.8,
 77                                            label=ba.Lstr(resource='saveText'))
 78        ba.widget(edit=save_button, left_widget=cancel_button)
 79        ba.widget(edit=cancel_button, right_widget=save_button)
 80        ba.containerwidget(edit=self._root_widget, start_button=btn)
 81        ba.textwidget(parent=self._root_widget,
 82                      position=(self._width * 0.5, height - 38),
 83                      size=(0, 0),
 84                      text=(ba.Lstr(resource=self._r + '.titleNewText')
 85                            if existing_profile is None else ba.Lstr(
 86                                resource=self._r + '.titleEditText')),
 87                      color=ba.app.ui.title_color,
 88                      maxwidth=290,
 89                      scale=1.0,
 90                      h_align='center',
 91                      v_align='center')
 92
 93        # Make a list of spaz icons.
 94        self.refresh_characters()
 95        profile = ba.app.config.get('Player Profiles',
 96                                    {}).get(self._existing_profile, {})
 97
 98        if 'global' in profile:
 99            self._global = profile['global']
100        else:
101            self._global = False
102
103        if 'icon' in profile:
104            self._icon = profile['icon']
105        else:
106            self._icon = ba.charstr(ba.SpecialChar.LOGO)
107
108        assigned_random_char = False
109
110        # Look for existing character choice or pick random one otherwise.
111        try:
112            icon_index = self._spazzes.index(profile['character'])
113        except Exception:
114            # Let's set the default icon to spaz for our first profile; after
115            # that we go random.
116            # (SCRATCH THAT.. we now hard-code account-profiles to start with
117            # spaz which has a similar effect)
118            # try: p_len = len(ba.app.config['Player Profiles'])
119            # except Exception: p_len = 0
120            # if p_len == 0: icon_index = self._spazzes.index('Spaz')
121            # else:
122            random.seed()
123            icon_index = random.randrange(len(self._spazzes))
124            assigned_random_char = True
125        self._icon_index = icon_index
126        ba.buttonwidget(edit=save_button, on_activate_call=self.save)
127
128        v = height - 115.0
129        self._name = ('' if self._existing_profile is None else
130                      self._existing_profile)
131        self._is_account_profile = (self._name == '__account__')
132
133        # If we just picked a random character, see if it has specific
134        # colors/highlights associated with it and assign them if so.
135        if assigned_random_char:
136            clr = ba.app.spaz_appearances[
137                self._spazzes[icon_index]].default_color
138            if clr is not None:
139                self._color = clr
140            highlight = ba.app.spaz_appearances[
141                self._spazzes[icon_index]].default_highlight
142            if highlight is not None:
143                self._highlight = highlight
144
145        # Assign a random name if they had none.
146        if self._name == '':
147            names = _ba.get_random_names()
148            self._name = names[random.randrange(len(names))]
149
150        self._clipped_name_text = ba.textwidget(parent=self._root_widget,
151                                                text='',
152                                                position=(540 + x_inset,
153                                                          v - 8),
154                                                flatness=1.0,
155                                                shadow=0.0,
156                                                scale=0.55,
157                                                size=(0, 0),
158                                                maxwidth=100,
159                                                h_align='center',
160                                                v_align='center',
161                                                color=(1, 1, 0, 0.5))
162
163        if not self._is_account_profile and not self._global:
164            ba.textwidget(parent=self._root_widget,
165                          text=ba.Lstr(resource=self._r + '.nameText'),
166                          position=(200 + x_inset, v - 6),
167                          size=(0, 0),
168                          h_align='right',
169                          v_align='center',
170                          color=(1, 1, 1, 0.5),
171                          scale=0.9)
172
173        self._upgrade_button = None
174        if self._is_account_profile:
175            if _ba.get_v1_account_state() == 'signed_in':
176                sval = _ba.get_v1_account_display_string()
177            else:
178                sval = '??'
179            ba.textwidget(parent=self._root_widget,
180                          position=(self._width * 0.5, v - 7),
181                          size=(0, 0),
182                          scale=1.2,
183                          text=sval,
184                          maxwidth=270,
185                          h_align='center',
186                          v_align='center')
187            txtl = ba.Lstr(
188                resource='editProfileWindow.accountProfileText').evaluate()
189            b_width = min(
190                270.0,
191                _ba.get_string_width(txtl, suppress_warning=True) * 0.6)
192            ba.textwidget(parent=self._root_widget,
193                          position=(self._width * 0.5, v - 39),
194                          size=(0, 0),
195                          scale=0.6,
196                          color=ba.app.ui.infotextcolor,
197                          text=txtl,
198                          maxwidth=270,
199                          h_align='center',
200                          v_align='center')
201            self._account_type_info_button = ba.buttonwidget(
202                parent=self._root_widget,
203                label='?',
204                size=(15, 15),
205                text_scale=0.6,
206                position=(self._width * 0.5 + b_width * 0.5 + 13, v - 47),
207                button_type='square',
208                color=(0.6, 0.5, 0.65),
209                autoselect=True,
210                on_activate_call=self.show_account_profile_info)
211        elif self._global:
212
213            b_size = 60
214            self._icon_button = btn = ba.buttonwidget(
215                parent=self._root_widget,
216                autoselect=True,
217                position=(self._width * 0.5 - 160 - b_size * 0.5, v - 38 - 15),
218                size=(b_size, b_size),
219                color=(0.6, 0.5, 0.6),
220                label='',
221                button_type='square',
222                text_scale=1.2,
223                on_activate_call=self._on_icon_press)
224            self._icon_button_label = ba.textwidget(
225                parent=self._root_widget,
226                position=(self._width * 0.5 - 160, v - 35),
227                draw_controller=btn,
228                h_align='center',
229                v_align='center',
230                size=(0, 0),
231                color=(1, 1, 1),
232                text='',
233                scale=2.0)
234
235            ba.textwidget(parent=self._root_widget,
236                          h_align='center',
237                          v_align='center',
238                          position=(self._width * 0.5 - 160, v - 55 - 15),
239                          size=(0, 0),
240                          draw_controller=btn,
241                          text=ba.Lstr(resource=self._r + '.iconText'),
242                          scale=0.7,
243                          color=ba.app.ui.title_color,
244                          maxwidth=120)
245
246            self._update_icon()
247
248            ba.textwidget(parent=self._root_widget,
249                          position=(self._width * 0.5, v - 7),
250                          size=(0, 0),
251                          scale=1.2,
252                          text=self._name,
253                          maxwidth=240,
254                          h_align='center',
255                          v_align='center')
256            # FIXME hard coded strings are bad
257            txtl = ba.Lstr(
258                resource='editProfileWindow.globalProfileText').evaluate()
259            b_width = min(
260                240.0,
261                _ba.get_string_width(txtl, suppress_warning=True) * 0.6)
262            ba.textwidget(parent=self._root_widget,
263                          position=(self._width * 0.5, v - 39),
264                          size=(0, 0),
265                          scale=0.6,
266                          color=ba.app.ui.infotextcolor,
267                          text=txtl,
268                          maxwidth=240,
269                          h_align='center',
270                          v_align='center')
271            self._account_type_info_button = ba.buttonwidget(
272                parent=self._root_widget,
273                label='?',
274                size=(15, 15),
275                text_scale=0.6,
276                position=(self._width * 0.5 + b_width * 0.5 + 13, v - 47),
277                button_type='square',
278                color=(0.6, 0.5, 0.65),
279                autoselect=True,
280                on_activate_call=self.show_global_profile_info)
281        else:
282            self._text_field = ba.textwidget(
283                parent=self._root_widget,
284                position=(220 + x_inset, v - 30),
285                size=(265, 40),
286                text=self._name,
287                h_align='left',
288                v_align='center',
289                max_chars=16,
290                description=ba.Lstr(resource=self._r + '.nameDescriptionText'),
291                autoselect=True,
292                editable=True,
293                padding=4,
294                color=(0.9, 0.9, 0.9, 1.0),
295                on_return_press_call=ba.Call(save_button.activate))
296
297            # FIXME hard coded strings are bad
298            txtl = ba.Lstr(
299                resource='editProfileWindow.localProfileText').evaluate()
300            b_width = min(
301                270.0,
302                _ba.get_string_width(txtl, suppress_warning=True) * 0.6)
303            ba.textwidget(parent=self._root_widget,
304                          position=(self._width * 0.5, v - 43),
305                          size=(0, 0),
306                          scale=0.6,
307                          color=ba.app.ui.infotextcolor,
308                          text=txtl,
309                          maxwidth=270,
310                          h_align='center',
311                          v_align='center')
312            self._account_type_info_button = ba.buttonwidget(
313                parent=self._root_widget,
314                label='?',
315                size=(15, 15),
316                text_scale=0.6,
317                position=(self._width * 0.5 + b_width * 0.5 + 13, v - 50),
318                button_type='square',
319                color=(0.6, 0.5, 0.65),
320                autoselect=True,
321                on_activate_call=self.show_local_profile_info)
322            self._upgrade_button = ba.buttonwidget(
323                parent=self._root_widget,
324                label=ba.Lstr(resource='upgradeText'),
325                size=(40, 17),
326                text_scale=1.0,
327                button_type='square',
328                position=(self._width * 0.5 + b_width * 0.5 + 13 + 43, v - 51),
329                color=(0.6, 0.5, 0.65),
330                autoselect=True,
331                on_activate_call=self.upgrade_profile)
332
333        self._update_clipped_name()
334        self._clipped_name_timer = ba.Timer(0.333,
335                                            ba.WeakCall(
336                                                self._update_clipped_name),
337                                            timetype=ba.TimeType.REAL,
338                                            repeat=True)
339
340        v -= spacing * 3.0
341        b_size = 80
342        b_size_2 = 100
343        b_offs = 150
344        self._color_button = btn = ba.buttonwidget(
345            parent=self._root_widget,
346            autoselect=True,
347            position=(self._width * 0.5 - b_offs - b_size * 0.5, v - 50),
348            size=(b_size, b_size),
349            color=self._color,
350            label='',
351            button_type='square')
352        origin = self._color_button.get_screen_space_center()
353        ba.buttonwidget(edit=self._color_button,
354                        on_activate_call=ba.WeakCall(self._make_picker,
355                                                     'color', origin))
356        ba.textwidget(parent=self._root_widget,
357                      h_align='center',
358                      v_align='center',
359                      position=(self._width * 0.5 - b_offs, v - 65),
360                      size=(0, 0),
361                      draw_controller=btn,
362                      text=ba.Lstr(resource=self._r + '.colorText'),
363                      scale=0.7,
364                      color=ba.app.ui.title_color,
365                      maxwidth=120)
366
367        self._character_button = btn = ba.buttonwidget(
368            parent=self._root_widget,
369            autoselect=True,
370            position=(self._width * 0.5 - b_size_2 * 0.5, v - 60),
371            up_widget=self._account_type_info_button,
372            on_activate_call=self._on_character_press,
373            size=(b_size_2, b_size_2),
374            label='',
375            color=(1, 1, 1),
376            mask_texture=ba.gettexture('characterIconMask'))
377        if not self._is_account_profile and not self._global:
378            ba.containerwidget(edit=self._root_widget,
379                               selected_child=self._text_field)
380        ba.textwidget(parent=self._root_widget,
381                      h_align='center',
382                      v_align='center',
383                      position=(self._width * 0.5, v - 80),
384                      size=(0, 0),
385                      draw_controller=btn,
386                      text=ba.Lstr(resource=self._r + '.characterText'),
387                      scale=0.7,
388                      color=ba.app.ui.title_color,
389                      maxwidth=130)
390
391        self._highlight_button = btn = ba.buttonwidget(
392            parent=self._root_widget,
393            autoselect=True,
394            position=(self._width * 0.5 + b_offs - b_size * 0.5, v - 50),
395            up_widget=self._upgrade_button if self._upgrade_button is not None
396            else self._account_type_info_button,
397            size=(b_size, b_size),
398            color=self._highlight,
399            label='',
400            button_type='square')
401
402        if not self._is_account_profile and not self._global:
403            ba.widget(edit=cancel_button, down_widget=self._text_field)
404            ba.widget(edit=save_button, down_widget=self._text_field)
405            ba.widget(edit=self._color_button, up_widget=self._text_field)
406        ba.widget(edit=self._account_type_info_button,
407                  down_widget=self._character_button)
408
409        origin = self._highlight_button.get_screen_space_center()
410        ba.buttonwidget(edit=self._highlight_button,
411                        on_activate_call=ba.WeakCall(self._make_picker,
412                                                     'highlight', origin))
413        ba.textwidget(parent=self._root_widget,
414                      h_align='center',
415                      v_align='center',
416                      position=(self._width * 0.5 + b_offs, v - 65),
417                      size=(0, 0),
418                      draw_controller=btn,
419                      text=ba.Lstr(resource=self._r + '.highlightText'),
420                      scale=0.7,
421                      color=ba.app.ui.title_color,
422                      maxwidth=120)
423        self._update_character()
424
425    def upgrade_profile(self) -> None:
426        """Attempt to ugrade the profile to global."""
427        from bastd.ui import account
428        from bastd.ui.profile import upgrade as pupgrade
429        if _ba.get_v1_account_state() != 'signed_in':
430            account.show_sign_in_prompt()
431            return
432
433        pupgrade.ProfileUpgradeWindow(self)
434
435    def show_account_profile_info(self) -> None:
436        """Show an explanation of account profiles."""
437        from bastd.ui.confirm import ConfirmWindow
438        icons_str = ' '.join([
439            ba.charstr(n) for n in [
440                ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO,
441                ba.SpecialChar.GAME_CENTER_LOGO,
442                ba.SpecialChar.GAME_CIRCLE_LOGO, ba.SpecialChar.OUYA_LOGO,
443                ba.SpecialChar.LOCAL_ACCOUNT, ba.SpecialChar.ALIBABA_LOGO,
444                ba.SpecialChar.OCULUS_LOGO, ba.SpecialChar.NVIDIA_LOGO
445            ]
446        ])
447        txtl = ba.Lstr(resource='editProfileWindow.accountProfileInfoText',
448                       subs=[('${ICONS}', icons_str)])
449        ConfirmWindow(txtl,
450                      cancel_button=False,
451                      width=500,
452                      height=300,
453                      origin_widget=self._account_type_info_button)
454
455    def show_local_profile_info(self) -> None:
456        """Show an explanation of local profiles."""
457        from bastd.ui.confirm import ConfirmWindow
458        txtl = ba.Lstr(resource='editProfileWindow.localProfileInfoText')
459        ConfirmWindow(txtl,
460                      cancel_button=False,
461                      width=600,
462                      height=250,
463                      origin_widget=self._account_type_info_button)
464
465    def show_global_profile_info(self) -> None:
466        """Show an explanation of global profiles."""
467        from bastd.ui.confirm import ConfirmWindow
468        txtl = ba.Lstr(resource='editProfileWindow.globalProfileInfoText')
469        ConfirmWindow(txtl,
470                      cancel_button=False,
471                      width=600,
472                      height=250,
473                      origin_widget=self._account_type_info_button)
474
475    def refresh_characters(self) -> None:
476        """Refresh available characters/icons."""
477        from bastd.actor import spazappearance
478        self._spazzes = spazappearance.get_appearances()
479        self._spazzes.sort()
480        self._icon_textures = [
481            ba.gettexture(ba.app.spaz_appearances[s].icon_texture)
482            for s in self._spazzes
483        ]
484        self._icon_tint_textures = [
485            ba.gettexture(ba.app.spaz_appearances[s].icon_mask_texture)
486            for s in self._spazzes
487        ]
488
489    def on_icon_picker_pick(self, icon: str) -> None:
490        """An icon has been selected by the picker."""
491        self._icon = icon
492        self._update_icon()
493
494    def on_character_picker_pick(self, character: str) -> None:
495        """A character has been selected by the picker."""
496        if not self._root_widget:
497            return
498
499        # The player could have bought a new one while the picker was up.
500        self.refresh_characters()
501        self._icon_index = self._spazzes.index(
502            character) if character in self._spazzes else 0
503        self._update_character()
504
505    def _on_character_press(self) -> None:
506        from bastd.ui import characterpicker
507        characterpicker.CharacterPicker(
508            parent=self._root_widget,
509            position=self._character_button.get_screen_space_center(),
510            selected_character=self._spazzes[self._icon_index],
511            delegate=self,
512            tint_color=self._color,
513            tint2_color=self._highlight)
514
515    def _on_icon_press(self) -> None:
516        from bastd.ui import iconpicker
517        iconpicker.IconPicker(
518            parent=self._root_widget,
519            position=self._icon_button.get_screen_space_center(),
520            selected_icon=self._icon,
521            delegate=self,
522            tint_color=self._color,
523            tint2_color=self._highlight)
524
525    def _make_picker(self, picker_type: str, origin: tuple[float,
526                                                           float]) -> None:
527        from bastd.ui import colorpicker
528        if picker_type == 'color':
529            initial_color = self._color
530        elif picker_type == 'highlight':
531            initial_color = self._highlight
532        else:
533            raise ValueError('invalid picker_type: ' + picker_type)
534        colorpicker.ColorPicker(
535            parent=self._root_widget,
536            position=origin,
537            offset=(self._base_scale *
538                    (-100 if picker_type == 'color' else 100), 0),
539            initial_color=initial_color,
540            delegate=self,
541            tag=picker_type)
542
543    def _cancel(self) -> None:
544        from bastd.ui.profile.browser import ProfileBrowserWindow
545        ba.containerwidget(edit=self._root_widget, transition='out_right')
546        ba.app.ui.set_main_menu_window(
547            ProfileBrowserWindow(
548                'in_left',
549                selected_profile=self._existing_profile,
550                in_main_menu=self._in_main_menu).get_root_widget())
551
552    def _set_color(self, color: tuple[float, float, float]) -> None:
553        self._color = color
554        if self._color_button:
555            ba.buttonwidget(edit=self._color_button, color=color)
556
557    def _set_highlight(self, color: tuple[float, float, float]) -> None:
558        self._highlight = color
559        if self._highlight_button:
560            ba.buttonwidget(edit=self._highlight_button, color=color)
561
562    def color_picker_closing(self, picker: ColorPicker) -> None:
563        """Called when a color picker is closing."""
564        if not self._root_widget:
565            return
566        tag = picker.get_tag()
567        if tag == 'color':
568            ba.containerwidget(edit=self._root_widget,
569                               selected_child=self._color_button)
570        elif tag == 'highlight':
571            ba.containerwidget(edit=self._root_widget,
572                               selected_child=self._highlight_button)
573        else:
574            print('color_picker_closing got unknown tag ' + str(tag))
575
576    def color_picker_selected_color(self, picker: ColorPicker,
577                                    color: tuple[float, float, float]) -> None:
578        """Called when a color is selected in a color picker."""
579        if not self._root_widget:
580            return
581        tag = picker.get_tag()
582        if tag == 'color':
583            self._set_color(color)
584        elif tag == 'highlight':
585            self._set_highlight(color)
586        else:
587            print('color_picker_selected_color got unknown tag ' + str(tag))
588        self._update_character()
589
590    def _update_clipped_name(self) -> None:
591        if not self._clipped_name_text:
592            return
593        name = self.getname()
594        if name == '__account__':
595            name = (_ba.get_v1_account_name()
596                    if _ba.get_v1_account_state() == 'signed_in' else '???')
597        if len(name) > 10 and not (self._global or self._is_account_profile):
598            ba.textwidget(edit=self._clipped_name_text,
599                          text=ba.Lstr(resource='inGameClippedNameText',
600                                       subs=[('${NAME}', name[:10] + '...')]))
601        else:
602            ba.textwidget(edit=self._clipped_name_text, text='')
603
604    def _update_character(self, change: int = 0) -> None:
605        self._icon_index = (self._icon_index + change) % len(self._spazzes)
606        if self._character_button:
607            ba.buttonwidget(
608                edit=self._character_button,
609                texture=self._icon_textures[self._icon_index],
610                tint_texture=self._icon_tint_textures[self._icon_index],
611                tint_color=self._color,
612                tint2_color=self._highlight)
613
614    def _update_icon(self) -> None:
615        if self._icon_button_label:
616            ba.textwidget(edit=self._icon_button_label, text=self._icon)
617
618    def getname(self) -> str:
619        """Return the current profile name value."""
620        if self._is_account_profile:
621            new_name = '__account__'
622        elif self._global:
623            new_name = self._name
624        else:
625            new_name = cast(str, ba.textwidget(query=self._text_field))
626        return new_name
627
628    def save(self, transition_out: bool = True) -> bool:
629        """Save has been selected."""
630        from bastd.ui.profile.browser import ProfileBrowserWindow
631        new_name = self.getname().strip()
632
633        if not new_name:
634            ba.screenmessage(ba.Lstr(resource='nameNotEmptyText'))
635            ba.playsound(ba.getsound('error'))
636            return False
637
638        if transition_out:
639            ba.playsound(ba.getsound('gunCocking'))
640
641        # Delete old in case we're renaming.
642        if self._existing_profile and self._existing_profile != new_name:
643            _ba.add_transaction({
644                'type': 'REMOVE_PLAYER_PROFILE',
645                'name': self._existing_profile
646            })
647
648            # Also lets be aware we're no longer global if we're taking a
649            # new name (will need to re-request it).
650            self._global = False
651
652        _ba.add_transaction({
653            'type': 'ADD_PLAYER_PROFILE',
654            'name': new_name,
655            'profile': {
656                'character': self._spazzes[self._icon_index],
657                'color': list(self._color),
658                'global': self._global,
659                'icon': self._icon,
660                'highlight': list(self._highlight)
661            }
662        })
663
664        if transition_out:
665            _ba.run_transactions()
666            ba.containerwidget(edit=self._root_widget, transition='out_right')
667            ba.app.ui.set_main_menu_window(
668                ProfileBrowserWindow(
669                    'in_left',
670                    selected_profile=new_name,
671                    in_main_menu=self._in_main_menu).get_root_widget())
672        return True
class EditProfileWindow(ba.ui.Window):
 18class EditProfileWindow(ba.Window):
 19    """Window for editing a player profile."""
 20
 21    # FIXME: WILL NEED TO CHANGE THIS FOR UILOCATION.
 22    def reload_window(self) -> None:
 23        """Transitions out and recreates ourself."""
 24        ba.containerwidget(edit=self._root_widget, transition='out_left')
 25        ba.app.ui.set_main_menu_window(
 26            EditProfileWindow(self.getname(),
 27                              self._in_main_menu).get_root_widget())
 28
 29    def __init__(self,
 30                 existing_profile: str | None,
 31                 in_main_menu: bool,
 32                 transition: str = 'in_right'):
 33        # FIXME: Tidy this up a bit.
 34        # pylint: disable=too-many-branches
 35        # pylint: disable=too-many-statements
 36        # pylint: disable=too-many-locals
 37        from ba.internal import get_player_profile_colors
 38        self._in_main_menu = in_main_menu
 39        self._existing_profile = existing_profile
 40        self._r = 'editProfileWindow'
 41        self._spazzes: list[str] = []
 42        self._icon_textures: list[ba.Texture] = []
 43        self._icon_tint_textures: list[ba.Texture] = []
 44
 45        # Grab profile colors or pick random ones.
 46        self._color, self._highlight = get_player_profile_colors(
 47            existing_profile)
 48        uiscale = ba.app.ui.uiscale
 49        self._width = width = 780.0 if uiscale is ba.UIScale.SMALL else 680.0
 50        self._x_inset = x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0
 51        self._height = height = (
 52            350.0 if uiscale is ba.UIScale.SMALL else
 53            400.0 if uiscale is ba.UIScale.MEDIUM else 450.0)
 54        spacing = 40
 55        self._base_scale = (2.05 if uiscale is ba.UIScale.SMALL else
 56                            1.5 if uiscale is ba.UIScale.MEDIUM else 1.0)
 57        top_extra = 15 if uiscale is ba.UIScale.SMALL else 15
 58        super().__init__(root_widget=ba.containerwidget(
 59            size=(width, height + top_extra),
 60            transition=transition,
 61            scale=self._base_scale,
 62            stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0)))
 63        cancel_button = btn = ba.buttonwidget(
 64            parent=self._root_widget,
 65            position=(52 + x_inset, height - 60),
 66            size=(155, 60),
 67            scale=0.8,
 68            autoselect=True,
 69            label=ba.Lstr(resource='cancelText'),
 70            on_activate_call=self._cancel)
 71        ba.containerwidget(edit=self._root_widget, cancel_button=btn)
 72        save_button = btn = ba.buttonwidget(parent=self._root_widget,
 73                                            position=(width - (177 + x_inset),
 74                                                      height - 60),
 75                                            size=(155, 60),
 76                                            autoselect=True,
 77                                            scale=0.8,
 78                                            label=ba.Lstr(resource='saveText'))
 79        ba.widget(edit=save_button, left_widget=cancel_button)
 80        ba.widget(edit=cancel_button, right_widget=save_button)
 81        ba.containerwidget(edit=self._root_widget, start_button=btn)
 82        ba.textwidget(parent=self._root_widget,
 83                      position=(self._width * 0.5, height - 38),
 84                      size=(0, 0),
 85                      text=(ba.Lstr(resource=self._r + '.titleNewText')
 86                            if existing_profile is None else ba.Lstr(
 87                                resource=self._r + '.titleEditText')),
 88                      color=ba.app.ui.title_color,
 89                      maxwidth=290,
 90                      scale=1.0,
 91                      h_align='center',
 92                      v_align='center')
 93
 94        # Make a list of spaz icons.
 95        self.refresh_characters()
 96        profile = ba.app.config.get('Player Profiles',
 97                                    {}).get(self._existing_profile, {})
 98
 99        if 'global' in profile:
100            self._global = profile['global']
101        else:
102            self._global = False
103
104        if 'icon' in profile:
105            self._icon = profile['icon']
106        else:
107            self._icon = ba.charstr(ba.SpecialChar.LOGO)
108
109        assigned_random_char = False
110
111        # Look for existing character choice or pick random one otherwise.
112        try:
113            icon_index = self._spazzes.index(profile['character'])
114        except Exception:
115            # Let's set the default icon to spaz for our first profile; after
116            # that we go random.
117            # (SCRATCH THAT.. we now hard-code account-profiles to start with
118            # spaz which has a similar effect)
119            # try: p_len = len(ba.app.config['Player Profiles'])
120            # except Exception: p_len = 0
121            # if p_len == 0: icon_index = self._spazzes.index('Spaz')
122            # else:
123            random.seed()
124            icon_index = random.randrange(len(self._spazzes))
125            assigned_random_char = True
126        self._icon_index = icon_index
127        ba.buttonwidget(edit=save_button, on_activate_call=self.save)
128
129        v = height - 115.0
130        self._name = ('' if self._existing_profile is None else
131                      self._existing_profile)
132        self._is_account_profile = (self._name == '__account__')
133
134        # If we just picked a random character, see if it has specific
135        # colors/highlights associated with it and assign them if so.
136        if assigned_random_char:
137            clr = ba.app.spaz_appearances[
138                self._spazzes[icon_index]].default_color
139            if clr is not None:
140                self._color = clr
141            highlight = ba.app.spaz_appearances[
142                self._spazzes[icon_index]].default_highlight
143            if highlight is not None:
144                self._highlight = highlight
145
146        # Assign a random name if they had none.
147        if self._name == '':
148            names = _ba.get_random_names()
149            self._name = names[random.randrange(len(names))]
150
151        self._clipped_name_text = ba.textwidget(parent=self._root_widget,
152                                                text='',
153                                                position=(540 + x_inset,
154                                                          v - 8),
155                                                flatness=1.0,
156                                                shadow=0.0,
157                                                scale=0.55,
158                                                size=(0, 0),
159                                                maxwidth=100,
160                                                h_align='center',
161                                                v_align='center',
162                                                color=(1, 1, 0, 0.5))
163
164        if not self._is_account_profile and not self._global:
165            ba.textwidget(parent=self._root_widget,
166                          text=ba.Lstr(resource=self._r + '.nameText'),
167                          position=(200 + x_inset, v - 6),
168                          size=(0, 0),
169                          h_align='right',
170                          v_align='center',
171                          color=(1, 1, 1, 0.5),
172                          scale=0.9)
173
174        self._upgrade_button = None
175        if self._is_account_profile:
176            if _ba.get_v1_account_state() == 'signed_in':
177                sval = _ba.get_v1_account_display_string()
178            else:
179                sval = '??'
180            ba.textwidget(parent=self._root_widget,
181                          position=(self._width * 0.5, v - 7),
182                          size=(0, 0),
183                          scale=1.2,
184                          text=sval,
185                          maxwidth=270,
186                          h_align='center',
187                          v_align='center')
188            txtl = ba.Lstr(
189                resource='editProfileWindow.accountProfileText').evaluate()
190            b_width = min(
191                270.0,
192                _ba.get_string_width(txtl, suppress_warning=True) * 0.6)
193            ba.textwidget(parent=self._root_widget,
194                          position=(self._width * 0.5, v - 39),
195                          size=(0, 0),
196                          scale=0.6,
197                          color=ba.app.ui.infotextcolor,
198                          text=txtl,
199                          maxwidth=270,
200                          h_align='center',
201                          v_align='center')
202            self._account_type_info_button = ba.buttonwidget(
203                parent=self._root_widget,
204                label='?',
205                size=(15, 15),
206                text_scale=0.6,
207                position=(self._width * 0.5 + b_width * 0.5 + 13, v - 47),
208                button_type='square',
209                color=(0.6, 0.5, 0.65),
210                autoselect=True,
211                on_activate_call=self.show_account_profile_info)
212        elif self._global:
213
214            b_size = 60
215            self._icon_button = btn = ba.buttonwidget(
216                parent=self._root_widget,
217                autoselect=True,
218                position=(self._width * 0.5 - 160 - b_size * 0.5, v - 38 - 15),
219                size=(b_size, b_size),
220                color=(0.6, 0.5, 0.6),
221                label='',
222                button_type='square',
223                text_scale=1.2,
224                on_activate_call=self._on_icon_press)
225            self._icon_button_label = ba.textwidget(
226                parent=self._root_widget,
227                position=(self._width * 0.5 - 160, v - 35),
228                draw_controller=btn,
229                h_align='center',
230                v_align='center',
231                size=(0, 0),
232                color=(1, 1, 1),
233                text='',
234                scale=2.0)
235
236            ba.textwidget(parent=self._root_widget,
237                          h_align='center',
238                          v_align='center',
239                          position=(self._width * 0.5 - 160, v - 55 - 15),
240                          size=(0, 0),
241                          draw_controller=btn,
242                          text=ba.Lstr(resource=self._r + '.iconText'),
243                          scale=0.7,
244                          color=ba.app.ui.title_color,
245                          maxwidth=120)
246
247            self._update_icon()
248
249            ba.textwidget(parent=self._root_widget,
250                          position=(self._width * 0.5, v - 7),
251                          size=(0, 0),
252                          scale=1.2,
253                          text=self._name,
254                          maxwidth=240,
255                          h_align='center',
256                          v_align='center')
257            # FIXME hard coded strings are bad
258            txtl = ba.Lstr(
259                resource='editProfileWindow.globalProfileText').evaluate()
260            b_width = min(
261                240.0,
262                _ba.get_string_width(txtl, suppress_warning=True) * 0.6)
263            ba.textwidget(parent=self._root_widget,
264                          position=(self._width * 0.5, v - 39),
265                          size=(0, 0),
266                          scale=0.6,
267                          color=ba.app.ui.infotextcolor,
268                          text=txtl,
269                          maxwidth=240,
270                          h_align='center',
271                          v_align='center')
272            self._account_type_info_button = ba.buttonwidget(
273                parent=self._root_widget,
274                label='?',
275                size=(15, 15),
276                text_scale=0.6,
277                position=(self._width * 0.5 + b_width * 0.5 + 13, v - 47),
278                button_type='square',
279                color=(0.6, 0.5, 0.65),
280                autoselect=True,
281                on_activate_call=self.show_global_profile_info)
282        else:
283            self._text_field = ba.textwidget(
284                parent=self._root_widget,
285                position=(220 + x_inset, v - 30),
286                size=(265, 40),
287                text=self._name,
288                h_align='left',
289                v_align='center',
290                max_chars=16,
291                description=ba.Lstr(resource=self._r + '.nameDescriptionText'),
292                autoselect=True,
293                editable=True,
294                padding=4,
295                color=(0.9, 0.9, 0.9, 1.0),
296                on_return_press_call=ba.Call(save_button.activate))
297
298            # FIXME hard coded strings are bad
299            txtl = ba.Lstr(
300                resource='editProfileWindow.localProfileText').evaluate()
301            b_width = min(
302                270.0,
303                _ba.get_string_width(txtl, suppress_warning=True) * 0.6)
304            ba.textwidget(parent=self._root_widget,
305                          position=(self._width * 0.5, v - 43),
306                          size=(0, 0),
307                          scale=0.6,
308                          color=ba.app.ui.infotextcolor,
309                          text=txtl,
310                          maxwidth=270,
311                          h_align='center',
312                          v_align='center')
313            self._account_type_info_button = ba.buttonwidget(
314                parent=self._root_widget,
315                label='?',
316                size=(15, 15),
317                text_scale=0.6,
318                position=(self._width * 0.5 + b_width * 0.5 + 13, v - 50),
319                button_type='square',
320                color=(0.6, 0.5, 0.65),
321                autoselect=True,
322                on_activate_call=self.show_local_profile_info)
323            self._upgrade_button = ba.buttonwidget(
324                parent=self._root_widget,
325                label=ba.Lstr(resource='upgradeText'),
326                size=(40, 17),
327                text_scale=1.0,
328                button_type='square',
329                position=(self._width * 0.5 + b_width * 0.5 + 13 + 43, v - 51),
330                color=(0.6, 0.5, 0.65),
331                autoselect=True,
332                on_activate_call=self.upgrade_profile)
333
334        self._update_clipped_name()
335        self._clipped_name_timer = ba.Timer(0.333,
336                                            ba.WeakCall(
337                                                self._update_clipped_name),
338                                            timetype=ba.TimeType.REAL,
339                                            repeat=True)
340
341        v -= spacing * 3.0
342        b_size = 80
343        b_size_2 = 100
344        b_offs = 150
345        self._color_button = btn = ba.buttonwidget(
346            parent=self._root_widget,
347            autoselect=True,
348            position=(self._width * 0.5 - b_offs - b_size * 0.5, v - 50),
349            size=(b_size, b_size),
350            color=self._color,
351            label='',
352            button_type='square')
353        origin = self._color_button.get_screen_space_center()
354        ba.buttonwidget(edit=self._color_button,
355                        on_activate_call=ba.WeakCall(self._make_picker,
356                                                     'color', origin))
357        ba.textwidget(parent=self._root_widget,
358                      h_align='center',
359                      v_align='center',
360                      position=(self._width * 0.5 - b_offs, v - 65),
361                      size=(0, 0),
362                      draw_controller=btn,
363                      text=ba.Lstr(resource=self._r + '.colorText'),
364                      scale=0.7,
365                      color=ba.app.ui.title_color,
366                      maxwidth=120)
367
368        self._character_button = btn = ba.buttonwidget(
369            parent=self._root_widget,
370            autoselect=True,
371            position=(self._width * 0.5 - b_size_2 * 0.5, v - 60),
372            up_widget=self._account_type_info_button,
373            on_activate_call=self._on_character_press,
374            size=(b_size_2, b_size_2),
375            label='',
376            color=(1, 1, 1),
377            mask_texture=ba.gettexture('characterIconMask'))
378        if not self._is_account_profile and not self._global:
379            ba.containerwidget(edit=self._root_widget,
380                               selected_child=self._text_field)
381        ba.textwidget(parent=self._root_widget,
382                      h_align='center',
383                      v_align='center',
384                      position=(self._width * 0.5, v - 80),
385                      size=(0, 0),
386                      draw_controller=btn,
387                      text=ba.Lstr(resource=self._r + '.characterText'),
388                      scale=0.7,
389                      color=ba.app.ui.title_color,
390                      maxwidth=130)
391
392        self._highlight_button = btn = ba.buttonwidget(
393            parent=self._root_widget,
394            autoselect=True,
395            position=(self._width * 0.5 + b_offs - b_size * 0.5, v - 50),
396            up_widget=self._upgrade_button if self._upgrade_button is not None
397            else self._account_type_info_button,
398            size=(b_size, b_size),
399            color=self._highlight,
400            label='',
401            button_type='square')
402
403        if not self._is_account_profile and not self._global:
404            ba.widget(edit=cancel_button, down_widget=self._text_field)
405            ba.widget(edit=save_button, down_widget=self._text_field)
406            ba.widget(edit=self._color_button, up_widget=self._text_field)
407        ba.widget(edit=self._account_type_info_button,
408                  down_widget=self._character_button)
409
410        origin = self._highlight_button.get_screen_space_center()
411        ba.buttonwidget(edit=self._highlight_button,
412                        on_activate_call=ba.WeakCall(self._make_picker,
413                                                     'highlight', origin))
414        ba.textwidget(parent=self._root_widget,
415                      h_align='center',
416                      v_align='center',
417                      position=(self._width * 0.5 + b_offs, v - 65),
418                      size=(0, 0),
419                      draw_controller=btn,
420                      text=ba.Lstr(resource=self._r + '.highlightText'),
421                      scale=0.7,
422                      color=ba.app.ui.title_color,
423                      maxwidth=120)
424        self._update_character()
425
426    def upgrade_profile(self) -> None:
427        """Attempt to ugrade the profile to global."""
428        from bastd.ui import account
429        from bastd.ui.profile import upgrade as pupgrade
430        if _ba.get_v1_account_state() != 'signed_in':
431            account.show_sign_in_prompt()
432            return
433
434        pupgrade.ProfileUpgradeWindow(self)
435
436    def show_account_profile_info(self) -> None:
437        """Show an explanation of account profiles."""
438        from bastd.ui.confirm import ConfirmWindow
439        icons_str = ' '.join([
440            ba.charstr(n) for n in [
441                ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO,
442                ba.SpecialChar.GAME_CENTER_LOGO,
443                ba.SpecialChar.GAME_CIRCLE_LOGO, ba.SpecialChar.OUYA_LOGO,
444                ba.SpecialChar.LOCAL_ACCOUNT, ba.SpecialChar.ALIBABA_LOGO,
445                ba.SpecialChar.OCULUS_LOGO, ba.SpecialChar.NVIDIA_LOGO
446            ]
447        ])
448        txtl = ba.Lstr(resource='editProfileWindow.accountProfileInfoText',
449                       subs=[('${ICONS}', icons_str)])
450        ConfirmWindow(txtl,
451                      cancel_button=False,
452                      width=500,
453                      height=300,
454                      origin_widget=self._account_type_info_button)
455
456    def show_local_profile_info(self) -> None:
457        """Show an explanation of local profiles."""
458        from bastd.ui.confirm import ConfirmWindow
459        txtl = ba.Lstr(resource='editProfileWindow.localProfileInfoText')
460        ConfirmWindow(txtl,
461                      cancel_button=False,
462                      width=600,
463                      height=250,
464                      origin_widget=self._account_type_info_button)
465
466    def show_global_profile_info(self) -> None:
467        """Show an explanation of global profiles."""
468        from bastd.ui.confirm import ConfirmWindow
469        txtl = ba.Lstr(resource='editProfileWindow.globalProfileInfoText')
470        ConfirmWindow(txtl,
471                      cancel_button=False,
472                      width=600,
473                      height=250,
474                      origin_widget=self._account_type_info_button)
475
476    def refresh_characters(self) -> None:
477        """Refresh available characters/icons."""
478        from bastd.actor import spazappearance
479        self._spazzes = spazappearance.get_appearances()
480        self._spazzes.sort()
481        self._icon_textures = [
482            ba.gettexture(ba.app.spaz_appearances[s].icon_texture)
483            for s in self._spazzes
484        ]
485        self._icon_tint_textures = [
486            ba.gettexture(ba.app.spaz_appearances[s].icon_mask_texture)
487            for s in self._spazzes
488        ]
489
490    def on_icon_picker_pick(self, icon: str) -> None:
491        """An icon has been selected by the picker."""
492        self._icon = icon
493        self._update_icon()
494
495    def on_character_picker_pick(self, character: str) -> None:
496        """A character has been selected by the picker."""
497        if not self._root_widget:
498            return
499
500        # The player could have bought a new one while the picker was up.
501        self.refresh_characters()
502        self._icon_index = self._spazzes.index(
503            character) if character in self._spazzes else 0
504        self._update_character()
505
506    def _on_character_press(self) -> None:
507        from bastd.ui import characterpicker
508        characterpicker.CharacterPicker(
509            parent=self._root_widget,
510            position=self._character_button.get_screen_space_center(),
511            selected_character=self._spazzes[self._icon_index],
512            delegate=self,
513            tint_color=self._color,
514            tint2_color=self._highlight)
515
516    def _on_icon_press(self) -> None:
517        from bastd.ui import iconpicker
518        iconpicker.IconPicker(
519            parent=self._root_widget,
520            position=self._icon_button.get_screen_space_center(),
521            selected_icon=self._icon,
522            delegate=self,
523            tint_color=self._color,
524            tint2_color=self._highlight)
525
526    def _make_picker(self, picker_type: str, origin: tuple[float,
527                                                           float]) -> None:
528        from bastd.ui import colorpicker
529        if picker_type == 'color':
530            initial_color = self._color
531        elif picker_type == 'highlight':
532            initial_color = self._highlight
533        else:
534            raise ValueError('invalid picker_type: ' + picker_type)
535        colorpicker.ColorPicker(
536            parent=self._root_widget,
537            position=origin,
538            offset=(self._base_scale *
539                    (-100 if picker_type == 'color' else 100), 0),
540            initial_color=initial_color,
541            delegate=self,
542            tag=picker_type)
543
544    def _cancel(self) -> None:
545        from bastd.ui.profile.browser import ProfileBrowserWindow
546        ba.containerwidget(edit=self._root_widget, transition='out_right')
547        ba.app.ui.set_main_menu_window(
548            ProfileBrowserWindow(
549                'in_left',
550                selected_profile=self._existing_profile,
551                in_main_menu=self._in_main_menu).get_root_widget())
552
553    def _set_color(self, color: tuple[float, float, float]) -> None:
554        self._color = color
555        if self._color_button:
556            ba.buttonwidget(edit=self._color_button, color=color)
557
558    def _set_highlight(self, color: tuple[float, float, float]) -> None:
559        self._highlight = color
560        if self._highlight_button:
561            ba.buttonwidget(edit=self._highlight_button, color=color)
562
563    def color_picker_closing(self, picker: ColorPicker) -> None:
564        """Called when a color picker is closing."""
565        if not self._root_widget:
566            return
567        tag = picker.get_tag()
568        if tag == 'color':
569            ba.containerwidget(edit=self._root_widget,
570                               selected_child=self._color_button)
571        elif tag == 'highlight':
572            ba.containerwidget(edit=self._root_widget,
573                               selected_child=self._highlight_button)
574        else:
575            print('color_picker_closing got unknown tag ' + str(tag))
576
577    def color_picker_selected_color(self, picker: ColorPicker,
578                                    color: tuple[float, float, float]) -> None:
579        """Called when a color is selected in a color picker."""
580        if not self._root_widget:
581            return
582        tag = picker.get_tag()
583        if tag == 'color':
584            self._set_color(color)
585        elif tag == 'highlight':
586            self._set_highlight(color)
587        else:
588            print('color_picker_selected_color got unknown tag ' + str(tag))
589        self._update_character()
590
591    def _update_clipped_name(self) -> None:
592        if not self._clipped_name_text:
593            return
594        name = self.getname()
595        if name == '__account__':
596            name = (_ba.get_v1_account_name()
597                    if _ba.get_v1_account_state() == 'signed_in' else '???')
598        if len(name) > 10 and not (self._global or self._is_account_profile):
599            ba.textwidget(edit=self._clipped_name_text,
600                          text=ba.Lstr(resource='inGameClippedNameText',
601                                       subs=[('${NAME}', name[:10] + '...')]))
602        else:
603            ba.textwidget(edit=self._clipped_name_text, text='')
604
605    def _update_character(self, change: int = 0) -> None:
606        self._icon_index = (self._icon_index + change) % len(self._spazzes)
607        if self._character_button:
608            ba.buttonwidget(
609                edit=self._character_button,
610                texture=self._icon_textures[self._icon_index],
611                tint_texture=self._icon_tint_textures[self._icon_index],
612                tint_color=self._color,
613                tint2_color=self._highlight)
614
615    def _update_icon(self) -> None:
616        if self._icon_button_label:
617            ba.textwidget(edit=self._icon_button_label, text=self._icon)
618
619    def getname(self) -> str:
620        """Return the current profile name value."""
621        if self._is_account_profile:
622            new_name = '__account__'
623        elif self._global:
624            new_name = self._name
625        else:
626            new_name = cast(str, ba.textwidget(query=self._text_field))
627        return new_name
628
629    def save(self, transition_out: bool = True) -> bool:
630        """Save has been selected."""
631        from bastd.ui.profile.browser import ProfileBrowserWindow
632        new_name = self.getname().strip()
633
634        if not new_name:
635            ba.screenmessage(ba.Lstr(resource='nameNotEmptyText'))
636            ba.playsound(ba.getsound('error'))
637            return False
638
639        if transition_out:
640            ba.playsound(ba.getsound('gunCocking'))
641
642        # Delete old in case we're renaming.
643        if self._existing_profile and self._existing_profile != new_name:
644            _ba.add_transaction({
645                'type': 'REMOVE_PLAYER_PROFILE',
646                'name': self._existing_profile
647            })
648
649            # Also lets be aware we're no longer global if we're taking a
650            # new name (will need to re-request it).
651            self._global = False
652
653        _ba.add_transaction({
654            'type': 'ADD_PLAYER_PROFILE',
655            'name': new_name,
656            'profile': {
657                'character': self._spazzes[self._icon_index],
658                'color': list(self._color),
659                'global': self._global,
660                'icon': self._icon,
661                'highlight': list(self._highlight)
662            }
663        })
664
665        if transition_out:
666            _ba.run_transactions()
667            ba.containerwidget(edit=self._root_widget, transition='out_right')
668            ba.app.ui.set_main_menu_window(
669                ProfileBrowserWindow(
670                    'in_left',
671                    selected_profile=new_name,
672                    in_main_menu=self._in_main_menu).get_root_widget())
673        return True

Window for editing a player profile.

EditProfileWindow( existing_profile: str | None, in_main_menu: bool, transition: str = 'in_right')
 29    def __init__(self,
 30                 existing_profile: str | None,
 31                 in_main_menu: bool,
 32                 transition: str = 'in_right'):
 33        # FIXME: Tidy this up a bit.
 34        # pylint: disable=too-many-branches
 35        # pylint: disable=too-many-statements
 36        # pylint: disable=too-many-locals
 37        from ba.internal import get_player_profile_colors
 38        self._in_main_menu = in_main_menu
 39        self._existing_profile = existing_profile
 40        self._r = 'editProfileWindow'
 41        self._spazzes: list[str] = []
 42        self._icon_textures: list[ba.Texture] = []
 43        self._icon_tint_textures: list[ba.Texture] = []
 44
 45        # Grab profile colors or pick random ones.
 46        self._color, self._highlight = get_player_profile_colors(
 47            existing_profile)
 48        uiscale = ba.app.ui.uiscale
 49        self._width = width = 780.0 if uiscale is ba.UIScale.SMALL else 680.0
 50        self._x_inset = x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0
 51        self._height = height = (
 52            350.0 if uiscale is ba.UIScale.SMALL else
 53            400.0 if uiscale is ba.UIScale.MEDIUM else 450.0)
 54        spacing = 40
 55        self._base_scale = (2.05 if uiscale is ba.UIScale.SMALL else
 56                            1.5 if uiscale is ba.UIScale.MEDIUM else 1.0)
 57        top_extra = 15 if uiscale is ba.UIScale.SMALL else 15
 58        super().__init__(root_widget=ba.containerwidget(
 59            size=(width, height + top_extra),
 60            transition=transition,
 61            scale=self._base_scale,
 62            stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0)))
 63        cancel_button = btn = ba.buttonwidget(
 64            parent=self._root_widget,
 65            position=(52 + x_inset, height - 60),
 66            size=(155, 60),
 67            scale=0.8,
 68            autoselect=True,
 69            label=ba.Lstr(resource='cancelText'),
 70            on_activate_call=self._cancel)
 71        ba.containerwidget(edit=self._root_widget, cancel_button=btn)
 72        save_button = btn = ba.buttonwidget(parent=self._root_widget,
 73                                            position=(width - (177 + x_inset),
 74                                                      height - 60),
 75                                            size=(155, 60),
 76                                            autoselect=True,
 77                                            scale=0.8,
 78                                            label=ba.Lstr(resource='saveText'))
 79        ba.widget(edit=save_button, left_widget=cancel_button)
 80        ba.widget(edit=cancel_button, right_widget=save_button)
 81        ba.containerwidget(edit=self._root_widget, start_button=btn)
 82        ba.textwidget(parent=self._root_widget,
 83                      position=(self._width * 0.5, height - 38),
 84                      size=(0, 0),
 85                      text=(ba.Lstr(resource=self._r + '.titleNewText')
 86                            if existing_profile is None else ba.Lstr(
 87                                resource=self._r + '.titleEditText')),
 88                      color=ba.app.ui.title_color,
 89                      maxwidth=290,
 90                      scale=1.0,
 91                      h_align='center',
 92                      v_align='center')
 93
 94        # Make a list of spaz icons.
 95        self.refresh_characters()
 96        profile = ba.app.config.get('Player Profiles',
 97                                    {}).get(self._existing_profile, {})
 98
 99        if 'global' in profile:
100            self._global = profile['global']
101        else:
102            self._global = False
103
104        if 'icon' in profile:
105            self._icon = profile['icon']
106        else:
107            self._icon = ba.charstr(ba.SpecialChar.LOGO)
108
109        assigned_random_char = False
110
111        # Look for existing character choice or pick random one otherwise.
112        try:
113            icon_index = self._spazzes.index(profile['character'])
114        except Exception:
115            # Let's set the default icon to spaz for our first profile; after
116            # that we go random.
117            # (SCRATCH THAT.. we now hard-code account-profiles to start with
118            # spaz which has a similar effect)
119            # try: p_len = len(ba.app.config['Player Profiles'])
120            # except Exception: p_len = 0
121            # if p_len == 0: icon_index = self._spazzes.index('Spaz')
122            # else:
123            random.seed()
124            icon_index = random.randrange(len(self._spazzes))
125            assigned_random_char = True
126        self._icon_index = icon_index
127        ba.buttonwidget(edit=save_button, on_activate_call=self.save)
128
129        v = height - 115.0
130        self._name = ('' if self._existing_profile is None else
131                      self._existing_profile)
132        self._is_account_profile = (self._name == '__account__')
133
134        # If we just picked a random character, see if it has specific
135        # colors/highlights associated with it and assign them if so.
136        if assigned_random_char:
137            clr = ba.app.spaz_appearances[
138                self._spazzes[icon_index]].default_color
139            if clr is not None:
140                self._color = clr
141            highlight = ba.app.spaz_appearances[
142                self._spazzes[icon_index]].default_highlight
143            if highlight is not None:
144                self._highlight = highlight
145
146        # Assign a random name if they had none.
147        if self._name == '':
148            names = _ba.get_random_names()
149            self._name = names[random.randrange(len(names))]
150
151        self._clipped_name_text = ba.textwidget(parent=self._root_widget,
152                                                text='',
153                                                position=(540 + x_inset,
154                                                          v - 8),
155                                                flatness=1.0,
156                                                shadow=0.0,
157                                                scale=0.55,
158                                                size=(0, 0),
159                                                maxwidth=100,
160                                                h_align='center',
161                                                v_align='center',
162                                                color=(1, 1, 0, 0.5))
163
164        if not self._is_account_profile and not self._global:
165            ba.textwidget(parent=self._root_widget,
166                          text=ba.Lstr(resource=self._r + '.nameText'),
167                          position=(200 + x_inset, v - 6),
168                          size=(0, 0),
169                          h_align='right',
170                          v_align='center',
171                          color=(1, 1, 1, 0.5),
172                          scale=0.9)
173
174        self._upgrade_button = None
175        if self._is_account_profile:
176            if _ba.get_v1_account_state() == 'signed_in':
177                sval = _ba.get_v1_account_display_string()
178            else:
179                sval = '??'
180            ba.textwidget(parent=self._root_widget,
181                          position=(self._width * 0.5, v - 7),
182                          size=(0, 0),
183                          scale=1.2,
184                          text=sval,
185                          maxwidth=270,
186                          h_align='center',
187                          v_align='center')
188            txtl = ba.Lstr(
189                resource='editProfileWindow.accountProfileText').evaluate()
190            b_width = min(
191                270.0,
192                _ba.get_string_width(txtl, suppress_warning=True) * 0.6)
193            ba.textwidget(parent=self._root_widget,
194                          position=(self._width * 0.5, v - 39),
195                          size=(0, 0),
196                          scale=0.6,
197                          color=ba.app.ui.infotextcolor,
198                          text=txtl,
199                          maxwidth=270,
200                          h_align='center',
201                          v_align='center')
202            self._account_type_info_button = ba.buttonwidget(
203                parent=self._root_widget,
204                label='?',
205                size=(15, 15),
206                text_scale=0.6,
207                position=(self._width * 0.5 + b_width * 0.5 + 13, v - 47),
208                button_type='square',
209                color=(0.6, 0.5, 0.65),
210                autoselect=True,
211                on_activate_call=self.show_account_profile_info)
212        elif self._global:
213
214            b_size = 60
215            self._icon_button = btn = ba.buttonwidget(
216                parent=self._root_widget,
217                autoselect=True,
218                position=(self._width * 0.5 - 160 - b_size * 0.5, v - 38 - 15),
219                size=(b_size, b_size),
220                color=(0.6, 0.5, 0.6),
221                label='',
222                button_type='square',
223                text_scale=1.2,
224                on_activate_call=self._on_icon_press)
225            self._icon_button_label = ba.textwidget(
226                parent=self._root_widget,
227                position=(self._width * 0.5 - 160, v - 35),
228                draw_controller=btn,
229                h_align='center',
230                v_align='center',
231                size=(0, 0),
232                color=(1, 1, 1),
233                text='',
234                scale=2.0)
235
236            ba.textwidget(parent=self._root_widget,
237                          h_align='center',
238                          v_align='center',
239                          position=(self._width * 0.5 - 160, v - 55 - 15),
240                          size=(0, 0),
241                          draw_controller=btn,
242                          text=ba.Lstr(resource=self._r + '.iconText'),
243                          scale=0.7,
244                          color=ba.app.ui.title_color,
245                          maxwidth=120)
246
247            self._update_icon()
248
249            ba.textwidget(parent=self._root_widget,
250                          position=(self._width * 0.5, v - 7),
251                          size=(0, 0),
252                          scale=1.2,
253                          text=self._name,
254                          maxwidth=240,
255                          h_align='center',
256                          v_align='center')
257            # FIXME hard coded strings are bad
258            txtl = ba.Lstr(
259                resource='editProfileWindow.globalProfileText').evaluate()
260            b_width = min(
261                240.0,
262                _ba.get_string_width(txtl, suppress_warning=True) * 0.6)
263            ba.textwidget(parent=self._root_widget,
264                          position=(self._width * 0.5, v - 39),
265                          size=(0, 0),
266                          scale=0.6,
267                          color=ba.app.ui.infotextcolor,
268                          text=txtl,
269                          maxwidth=240,
270                          h_align='center',
271                          v_align='center')
272            self._account_type_info_button = ba.buttonwidget(
273                parent=self._root_widget,
274                label='?',
275                size=(15, 15),
276                text_scale=0.6,
277                position=(self._width * 0.5 + b_width * 0.5 + 13, v - 47),
278                button_type='square',
279                color=(0.6, 0.5, 0.65),
280                autoselect=True,
281                on_activate_call=self.show_global_profile_info)
282        else:
283            self._text_field = ba.textwidget(
284                parent=self._root_widget,
285                position=(220 + x_inset, v - 30),
286                size=(265, 40),
287                text=self._name,
288                h_align='left',
289                v_align='center',
290                max_chars=16,
291                description=ba.Lstr(resource=self._r + '.nameDescriptionText'),
292                autoselect=True,
293                editable=True,
294                padding=4,
295                color=(0.9, 0.9, 0.9, 1.0),
296                on_return_press_call=ba.Call(save_button.activate))
297
298            # FIXME hard coded strings are bad
299            txtl = ba.Lstr(
300                resource='editProfileWindow.localProfileText').evaluate()
301            b_width = min(
302                270.0,
303                _ba.get_string_width(txtl, suppress_warning=True) * 0.6)
304            ba.textwidget(parent=self._root_widget,
305                          position=(self._width * 0.5, v - 43),
306                          size=(0, 0),
307                          scale=0.6,
308                          color=ba.app.ui.infotextcolor,
309                          text=txtl,
310                          maxwidth=270,
311                          h_align='center',
312                          v_align='center')
313            self._account_type_info_button = ba.buttonwidget(
314                parent=self._root_widget,
315                label='?',
316                size=(15, 15),
317                text_scale=0.6,
318                position=(self._width * 0.5 + b_width * 0.5 + 13, v - 50),
319                button_type='square',
320                color=(0.6, 0.5, 0.65),
321                autoselect=True,
322                on_activate_call=self.show_local_profile_info)
323            self._upgrade_button = ba.buttonwidget(
324                parent=self._root_widget,
325                label=ba.Lstr(resource='upgradeText'),
326                size=(40, 17),
327                text_scale=1.0,
328                button_type='square',
329                position=(self._width * 0.5 + b_width * 0.5 + 13 + 43, v - 51),
330                color=(0.6, 0.5, 0.65),
331                autoselect=True,
332                on_activate_call=self.upgrade_profile)
333
334        self._update_clipped_name()
335        self._clipped_name_timer = ba.Timer(0.333,
336                                            ba.WeakCall(
337                                                self._update_clipped_name),
338                                            timetype=ba.TimeType.REAL,
339                                            repeat=True)
340
341        v -= spacing * 3.0
342        b_size = 80
343        b_size_2 = 100
344        b_offs = 150
345        self._color_button = btn = ba.buttonwidget(
346            parent=self._root_widget,
347            autoselect=True,
348            position=(self._width * 0.5 - b_offs - b_size * 0.5, v - 50),
349            size=(b_size, b_size),
350            color=self._color,
351            label='',
352            button_type='square')
353        origin = self._color_button.get_screen_space_center()
354        ba.buttonwidget(edit=self._color_button,
355                        on_activate_call=ba.WeakCall(self._make_picker,
356                                                     'color', origin))
357        ba.textwidget(parent=self._root_widget,
358                      h_align='center',
359                      v_align='center',
360                      position=(self._width * 0.5 - b_offs, v - 65),
361                      size=(0, 0),
362                      draw_controller=btn,
363                      text=ba.Lstr(resource=self._r + '.colorText'),
364                      scale=0.7,
365                      color=ba.app.ui.title_color,
366                      maxwidth=120)
367
368        self._character_button = btn = ba.buttonwidget(
369            parent=self._root_widget,
370            autoselect=True,
371            position=(self._width * 0.5 - b_size_2 * 0.5, v - 60),
372            up_widget=self._account_type_info_button,
373            on_activate_call=self._on_character_press,
374            size=(b_size_2, b_size_2),
375            label='',
376            color=(1, 1, 1),
377            mask_texture=ba.gettexture('characterIconMask'))
378        if not self._is_account_profile and not self._global:
379            ba.containerwidget(edit=self._root_widget,
380                               selected_child=self._text_field)
381        ba.textwidget(parent=self._root_widget,
382                      h_align='center',
383                      v_align='center',
384                      position=(self._width * 0.5, v - 80),
385                      size=(0, 0),
386                      draw_controller=btn,
387                      text=ba.Lstr(resource=self._r + '.characterText'),
388                      scale=0.7,
389                      color=ba.app.ui.title_color,
390                      maxwidth=130)
391
392        self._highlight_button = btn = ba.buttonwidget(
393            parent=self._root_widget,
394            autoselect=True,
395            position=(self._width * 0.5 + b_offs - b_size * 0.5, v - 50),
396            up_widget=self._upgrade_button if self._upgrade_button is not None
397            else self._account_type_info_button,
398            size=(b_size, b_size),
399            color=self._highlight,
400            label='',
401            button_type='square')
402
403        if not self._is_account_profile and not self._global:
404            ba.widget(edit=cancel_button, down_widget=self._text_field)
405            ba.widget(edit=save_button, down_widget=self._text_field)
406            ba.widget(edit=self._color_button, up_widget=self._text_field)
407        ba.widget(edit=self._account_type_info_button,
408                  down_widget=self._character_button)
409
410        origin = self._highlight_button.get_screen_space_center()
411        ba.buttonwidget(edit=self._highlight_button,
412                        on_activate_call=ba.WeakCall(self._make_picker,
413                                                     'highlight', origin))
414        ba.textwidget(parent=self._root_widget,
415                      h_align='center',
416                      v_align='center',
417                      position=(self._width * 0.5 + b_offs, v - 65),
418                      size=(0, 0),
419                      draw_controller=btn,
420                      text=ba.Lstr(resource=self._r + '.highlightText'),
421                      scale=0.7,
422                      color=ba.app.ui.title_color,
423                      maxwidth=120)
424        self._update_character()
def reload_window(self) -> None:
22    def reload_window(self) -> None:
23        """Transitions out and recreates ourself."""
24        ba.containerwidget(edit=self._root_widget, transition='out_left')
25        ba.app.ui.set_main_menu_window(
26            EditProfileWindow(self.getname(),
27                              self._in_main_menu).get_root_widget())

Transitions out and recreates ourself.

def upgrade_profile(self) -> None:
426    def upgrade_profile(self) -> None:
427        """Attempt to ugrade the profile to global."""
428        from bastd.ui import account
429        from bastd.ui.profile import upgrade as pupgrade
430        if _ba.get_v1_account_state() != 'signed_in':
431            account.show_sign_in_prompt()
432            return
433
434        pupgrade.ProfileUpgradeWindow(self)

Attempt to ugrade the profile to global.

def show_account_profile_info(self) -> None:
436    def show_account_profile_info(self) -> None:
437        """Show an explanation of account profiles."""
438        from bastd.ui.confirm import ConfirmWindow
439        icons_str = ' '.join([
440            ba.charstr(n) for n in [
441                ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO,
442                ba.SpecialChar.GAME_CENTER_LOGO,
443                ba.SpecialChar.GAME_CIRCLE_LOGO, ba.SpecialChar.OUYA_LOGO,
444                ba.SpecialChar.LOCAL_ACCOUNT, ba.SpecialChar.ALIBABA_LOGO,
445                ba.SpecialChar.OCULUS_LOGO, ba.SpecialChar.NVIDIA_LOGO
446            ]
447        ])
448        txtl = ba.Lstr(resource='editProfileWindow.accountProfileInfoText',
449                       subs=[('${ICONS}', icons_str)])
450        ConfirmWindow(txtl,
451                      cancel_button=False,
452                      width=500,
453                      height=300,
454                      origin_widget=self._account_type_info_button)

Show an explanation of account profiles.

def show_local_profile_info(self) -> None:
456    def show_local_profile_info(self) -> None:
457        """Show an explanation of local profiles."""
458        from bastd.ui.confirm import ConfirmWindow
459        txtl = ba.Lstr(resource='editProfileWindow.localProfileInfoText')
460        ConfirmWindow(txtl,
461                      cancel_button=False,
462                      width=600,
463                      height=250,
464                      origin_widget=self._account_type_info_button)

Show an explanation of local profiles.

def show_global_profile_info(self) -> None:
466    def show_global_profile_info(self) -> None:
467        """Show an explanation of global profiles."""
468        from bastd.ui.confirm import ConfirmWindow
469        txtl = ba.Lstr(resource='editProfileWindow.globalProfileInfoText')
470        ConfirmWindow(txtl,
471                      cancel_button=False,
472                      width=600,
473                      height=250,
474                      origin_widget=self._account_type_info_button)

Show an explanation of global profiles.

def refresh_characters(self) -> None:
476    def refresh_characters(self) -> None:
477        """Refresh available characters/icons."""
478        from bastd.actor import spazappearance
479        self._spazzes = spazappearance.get_appearances()
480        self._spazzes.sort()
481        self._icon_textures = [
482            ba.gettexture(ba.app.spaz_appearances[s].icon_texture)
483            for s in self._spazzes
484        ]
485        self._icon_tint_textures = [
486            ba.gettexture(ba.app.spaz_appearances[s].icon_mask_texture)
487            for s in self._spazzes
488        ]

Refresh available characters/icons.

def on_icon_picker_pick(self, icon: str) -> None:
490    def on_icon_picker_pick(self, icon: str) -> None:
491        """An icon has been selected by the picker."""
492        self._icon = icon
493        self._update_icon()

An icon has been selected by the picker.

def on_character_picker_pick(self, character: str) -> None:
495    def on_character_picker_pick(self, character: str) -> None:
496        """A character has been selected by the picker."""
497        if not self._root_widget:
498            return
499
500        # The player could have bought a new one while the picker was up.
501        self.refresh_characters()
502        self._icon_index = self._spazzes.index(
503            character) if character in self._spazzes else 0
504        self._update_character()

A character has been selected by the picker.

def color_picker_closing(self, picker: bastd.ui.colorpicker.ColorPicker) -> None:
563    def color_picker_closing(self, picker: ColorPicker) -> None:
564        """Called when a color picker is closing."""
565        if not self._root_widget:
566            return
567        tag = picker.get_tag()
568        if tag == 'color':
569            ba.containerwidget(edit=self._root_widget,
570                               selected_child=self._color_button)
571        elif tag == 'highlight':
572            ba.containerwidget(edit=self._root_widget,
573                               selected_child=self._highlight_button)
574        else:
575            print('color_picker_closing got unknown tag ' + str(tag))

Called when a color picker is closing.

def color_picker_selected_color( self, picker: bastd.ui.colorpicker.ColorPicker, color: tuple[float, float, float]) -> None:
577    def color_picker_selected_color(self, picker: ColorPicker,
578                                    color: tuple[float, float, float]) -> None:
579        """Called when a color is selected in a color picker."""
580        if not self._root_widget:
581            return
582        tag = picker.get_tag()
583        if tag == 'color':
584            self._set_color(color)
585        elif tag == 'highlight':
586            self._set_highlight(color)
587        else:
588            print('color_picker_selected_color got unknown tag ' + str(tag))
589        self._update_character()

Called when a color is selected in a color picker.

def getname(self) -> str:
619    def getname(self) -> str:
620        """Return the current profile name value."""
621        if self._is_account_profile:
622            new_name = '__account__'
623        elif self._global:
624            new_name = self._name
625        else:
626            new_name = cast(str, ba.textwidget(query=self._text_field))
627        return new_name

Return the current profile name value.

def save(self, transition_out: bool = True) -> bool:
629    def save(self, transition_out: bool = True) -> bool:
630        """Save has been selected."""
631        from bastd.ui.profile.browser import ProfileBrowserWindow
632        new_name = self.getname().strip()
633
634        if not new_name:
635            ba.screenmessage(ba.Lstr(resource='nameNotEmptyText'))
636            ba.playsound(ba.getsound('error'))
637            return False
638
639        if transition_out:
640            ba.playsound(ba.getsound('gunCocking'))
641
642        # Delete old in case we're renaming.
643        if self._existing_profile and self._existing_profile != new_name:
644            _ba.add_transaction({
645                'type': 'REMOVE_PLAYER_PROFILE',
646                'name': self._existing_profile
647            })
648
649            # Also lets be aware we're no longer global if we're taking a
650            # new name (will need to re-request it).
651            self._global = False
652
653        _ba.add_transaction({
654            'type': 'ADD_PLAYER_PROFILE',
655            'name': new_name,
656            'profile': {
657                'character': self._spazzes[self._icon_index],
658                'color': list(self._color),
659                'global': self._global,
660                'icon': self._icon,
661                'highlight': list(self._highlight)
662            }
663        })
664
665        if transition_out:
666            _ba.run_transactions()
667            ba.containerwidget(edit=self._root_widget, transition='out_right')
668            ba.app.ui.set_main_menu_window(
669                ProfileBrowserWindow(
670                    'in_left',
671                    selected_profile=new_name,
672                    in_main_menu=self._in_main_menu).get_root_widget())
673        return True

Save has been selected.

Inherited Members
ba.ui.Window
get_root_widget