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

Window for editing a player profile.

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

Transitions out and recreates ourself.

def upgrade_profile(self) -> None:
500    def upgrade_profile(self) -> None:
501        """Attempt to ugrade the profile to global."""
502        from bastd.ui import account
503        from bastd.ui.profile import upgrade as pupgrade
504
505        if ba.internal.get_v1_account_state() != 'signed_in':
506            account.show_sign_in_prompt()
507            return
508
509        pupgrade.ProfileUpgradeWindow(self)

Attempt to ugrade the profile to global.

def show_account_profile_info(self) -> None:
511    def show_account_profile_info(self) -> None:
512        """Show an explanation of account profiles."""
513        from bastd.ui.confirm import ConfirmWindow
514
515        icons_str = ' '.join(
516            [
517                ba.charstr(n)
518                for n in [
519                    ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO,
520                    ba.SpecialChar.GAME_CENTER_LOGO,
521                    ba.SpecialChar.GAME_CIRCLE_LOGO,
522                    ba.SpecialChar.OUYA_LOGO,
523                    ba.SpecialChar.LOCAL_ACCOUNT,
524                    ba.SpecialChar.ALIBABA_LOGO,
525                    ba.SpecialChar.OCULUS_LOGO,
526                    ba.SpecialChar.NVIDIA_LOGO,
527                ]
528            ]
529        )
530        txtl = ba.Lstr(
531            resource='editProfileWindow.accountProfileInfoText',
532            subs=[('${ICONS}', icons_str)],
533        )
534        ConfirmWindow(
535            txtl,
536            cancel_button=False,
537            width=500,
538            height=300,
539            origin_widget=self._account_type_info_button,
540        )

Show an explanation of account profiles.

def show_local_profile_info(self) -> None:
542    def show_local_profile_info(self) -> None:
543        """Show an explanation of local profiles."""
544        from bastd.ui.confirm import ConfirmWindow
545
546        txtl = ba.Lstr(resource='editProfileWindow.localProfileInfoText')
547        ConfirmWindow(
548            txtl,
549            cancel_button=False,
550            width=600,
551            height=250,
552            origin_widget=self._account_type_info_button,
553        )

Show an explanation of local profiles.

def show_global_profile_info(self) -> None:
555    def show_global_profile_info(self) -> None:
556        """Show an explanation of global profiles."""
557        from bastd.ui.confirm import ConfirmWindow
558
559        txtl = ba.Lstr(resource='editProfileWindow.globalProfileInfoText')
560        ConfirmWindow(
561            txtl,
562            cancel_button=False,
563            width=600,
564            height=250,
565            origin_widget=self._account_type_info_button,
566        )

Show an explanation of global profiles.

def refresh_characters(self) -> None:
568    def refresh_characters(self) -> None:
569        """Refresh available characters/icons."""
570        from bastd.actor import spazappearance
571
572        self._spazzes = spazappearance.get_appearances()
573        self._spazzes.sort()
574        self._icon_textures = [
575            ba.gettexture(ba.app.spaz_appearances[s].icon_texture)
576            for s in self._spazzes
577        ]
578        self._icon_tint_textures = [
579            ba.gettexture(ba.app.spaz_appearances[s].icon_mask_texture)
580            for s in self._spazzes
581        ]

Refresh available characters/icons.

def on_icon_picker_pick(self, icon: str) -> None:
583    def on_icon_picker_pick(self, icon: str) -> None:
584        """An icon has been selected by the picker."""
585        self._icon = icon
586        self._update_icon()

An icon has been selected by the picker.

def on_character_picker_pick(self, character: str) -> None:
588    def on_character_picker_pick(self, character: str) -> None:
589        """A character has been selected by the picker."""
590        if not self._root_widget:
591            return
592
593        # The player could have bought a new one while the picker was up.
594        self.refresh_characters()
595        self._icon_index = (
596            self._spazzes.index(character) if character in self._spazzes else 0
597        )
598        self._update_character()

A character has been selected by the picker.

def color_picker_closing(self, picker: bastd.ui.colorpicker.ColorPicker) -> None:
669    def color_picker_closing(self, picker: ColorPicker) -> None:
670        """Called when a color picker is closing."""
671        if not self._root_widget:
672            return
673        tag = picker.get_tag()
674        if tag == 'color':
675            ba.containerwidget(
676                edit=self._root_widget, selected_child=self._color_button
677            )
678        elif tag == 'highlight':
679            ba.containerwidget(
680                edit=self._root_widget, selected_child=self._highlight_button
681            )
682        else:
683            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:
685    def color_picker_selected_color(
686        self, picker: ColorPicker, color: tuple[float, float, float]
687    ) -> None:
688        """Called when a color is selected in a color picker."""
689        if not self._root_widget:
690            return
691        tag = picker.get_tag()
692        if tag == 'color':
693            self._set_color(color)
694        elif tag == 'highlight':
695            self._set_highlight(color)
696        else:
697            print('color_picker_selected_color got unknown tag ' + str(tag))
698        self._update_character()

Called when a color is selected in a color picker.

def getname(self) -> str:
736    def getname(self) -> str:
737        """Return the current profile name value."""
738        if self._is_account_profile:
739            new_name = '__account__'
740        elif self._global:
741            new_name = self._name
742        else:
743            new_name = cast(str, ba.textwidget(query=self._text_field))
744        return new_name

Return the current profile name value.

def save(self, transition_out: bool = True) -> bool:
746    def save(self, transition_out: bool = True) -> bool:
747        """Save has been selected."""
748        from bastd.ui.profile.browser import ProfileBrowserWindow
749
750        new_name = self.getname().strip()
751
752        if not new_name:
753            ba.screenmessage(ba.Lstr(resource='nameNotEmptyText'))
754            ba.playsound(ba.getsound('error'))
755            return False
756
757        if transition_out:
758            ba.playsound(ba.getsound('gunCocking'))
759
760        # Delete old in case we're renaming.
761        if self._existing_profile and self._existing_profile != new_name:
762            ba.internal.add_transaction(
763                {
764                    'type': 'REMOVE_PLAYER_PROFILE',
765                    'name': self._existing_profile,
766                }
767            )
768
769            # Also lets be aware we're no longer global if we're taking a
770            # new name (will need to re-request it).
771            self._global = False
772
773        ba.internal.add_transaction(
774            {
775                'type': 'ADD_PLAYER_PROFILE',
776                'name': new_name,
777                'profile': {
778                    'character': self._spazzes[self._icon_index],
779                    'color': list(self._color),
780                    'global': self._global,
781                    'icon': self._icon,
782                    'highlight': list(self._highlight),
783                },
784            }
785        )
786
787        if transition_out:
788            ba.internal.run_transactions()
789            ba.containerwidget(edit=self._root_widget, transition='out_right')
790            ba.app.ui.set_main_menu_window(
791                ProfileBrowserWindow(
792                    'in_left',
793                    selected_profile=new_name,
794                    in_main_menu=self._in_main_menu,
795                ).get_root_widget()
796            )
797        return True

Save has been selected.

Inherited Members
ba.ui.Window
get_root_widget