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

Window for editing a player profile.

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

Create a MainWindow given a root widget and transition info.

Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.

def reload_window(self) -> None:
23    def reload_window(self) -> None:
24        """Transitions out and recreates ourself."""
25
26        # no-op if we're not in control.
27        if not self.main_window_has_control():
28            return
29
30        # Replace ourself with ourself, but keep the same back location.
31        assert self.main_window_back_state is not None
32        self.main_window_replace(
33            EditProfileWindow(self.getname()),
34            back_state=self.main_window_back_state,
35        )

Transitions out and recreates ourself.

@override
def get_main_window_state(self) -> bauiv1.MainWindowState:
526    @override
527    def get_main_window_state(self) -> bui.MainWindowState:
528        # Support recreating our window for back/refresh purposes.
529        cls = type(self)
530
531        # Pull things out of self here; if we do it within the lambda
532        # we'll keep ourself alive which is bad.
533
534        existing_profile = self._existing_profile
535        return bui.BasicMainWindowState(
536            create_call=lambda transition, origin_widget: cls(
537                transition=transition,
538                origin_widget=origin_widget,
539                existing_profile=existing_profile,
540            )
541        )

Return a WindowState to recreate this window, if supported.

def assign_random_name(self) -> None:
543    def assign_random_name(self) -> None:
544        """Assigning a random name to the player."""
545        names = bs.get_random_names()
546        name = names[random.randrange(len(names))]
547        bui.textwidget(
548            edit=self._text_field,
549            text=name,
550        )

Assigning a random name to the player.

def upgrade_profile(self) -> None:
552    def upgrade_profile(self) -> None:
553        """Attempt to upgrade the profile to global."""
554        from bauiv1lib import account
555        from bauiv1lib.profile import upgrade as pupgrade
556
557        new_name = self.getname().strip()
558
559        if self._existing_profile and self._existing_profile != new_name:
560            bui.screenmessage(
561                'Unsaved changes found; you must save first.', color=(1, 0, 0)
562            )
563            bui.getsound('error').play()
564            return
565
566        plus = bui.app.plus
567        assert plus is not None
568
569        if plus.get_v1_account_state() != 'signed_in':
570            account.show_sign_in_prompt()
571            return
572
573        pupgrade.ProfileUpgradeWindow(self)

Attempt to upgrade the profile to global.

def show_account_profile_info(self) -> None:
575    def show_account_profile_info(self) -> None:
576        """Show an explanation of account profiles."""
577        from bauiv1lib.confirm import ConfirmWindow
578
579        icons_str = ' '.join(
580            [
581                bui.charstr(n)
582                for n in [
583                    bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO,
584                    bui.SpecialChar.GAME_CENTER_LOGO,
585                    bui.SpecialChar.LOCAL_ACCOUNT,
586                    bui.SpecialChar.OCULUS_LOGO,
587                    bui.SpecialChar.NVIDIA_LOGO,
588                    bui.SpecialChar.V2_LOGO,
589                ]
590            ]
591        )
592        txtl = bui.Lstr(
593            resource='editProfileWindow.accountProfileInfoText',
594            subs=[('${ICONS}', icons_str)],
595        )
596        ConfirmWindow(
597            txtl,
598            cancel_button=False,
599            width=500,
600            height=300,
601            origin_widget=self._account_type_info_button,
602        )

Show an explanation of account profiles.

def show_local_profile_info(self) -> None:
604    def show_local_profile_info(self) -> None:
605        """Show an explanation of local profiles."""
606        from bauiv1lib.confirm import ConfirmWindow
607
608        txtl = bui.Lstr(resource='editProfileWindow.localProfileInfoText')
609        ConfirmWindow(
610            txtl,
611            cancel_button=False,
612            width=600,
613            height=250,
614            origin_widget=self._account_type_info_button,
615        )

Show an explanation of local profiles.

def show_global_profile_info(self) -> None:
617    def show_global_profile_info(self) -> None:
618        """Show an explanation of global profiles."""
619        from bauiv1lib.confirm import ConfirmWindow
620
621        txtl = bui.Lstr(resource='editProfileWindow.globalProfileInfoText')
622        ConfirmWindow(
623            txtl,
624            cancel_button=False,
625            width=600,
626            height=250,
627            origin_widget=self._account_type_info_button,
628        )

Show an explanation of global profiles.

def refresh_characters(self) -> None:
630    def refresh_characters(self) -> None:
631        """Refresh available characters/icons."""
632        from bascenev1lib.actor import spazappearance
633
634        assert bui.app.classic is not None
635
636        self._spazzes = spazappearance.get_appearances()
637        self._spazzes.sort()
638        self._icon_textures = [
639            bui.gettexture(bui.app.classic.spaz_appearances[s].icon_texture)
640            for s in self._spazzes
641        ]
642        self._icon_tint_textures = [
643            bui.gettexture(
644                bui.app.classic.spaz_appearances[s].icon_mask_texture
645            )
646            for s in self._spazzes
647        ]

Refresh available characters/icons.

@override
def on_icon_picker_pick(self, icon: str) -> None:
649    @override
650    def on_icon_picker_pick(self, icon: str) -> None:
651        """An icon has been selected by the picker."""
652        self._icon = icon
653        self._update_icon()

An icon has been selected by the picker.

@override
def on_icon_picker_get_more_press(self) -> None:
655    @override
656    def on_icon_picker_get_more_press(self) -> None:
657        """User wants to get more icons."""
658        from bauiv1lib.store.browser import StoreBrowserWindow
659
660        if not self.main_window_has_control():
661            return
662
663        self.main_window_replace(
664            StoreBrowserWindow(
665                minimal_toolbars=True,
666                show_tab=StoreBrowserWindow.TabID.ICONS,
667            )
668        )

User wants to get more icons.

@override
def on_character_picker_pick(self, character: str) -> None:
670    @override
671    def on_character_picker_pick(self, character: str) -> None:
672        """A character has been selected by the picker."""
673        if not self._root_widget:
674            return
675
676        # The player could have bought a new one while the picker was
677        # up.
678        self.refresh_characters()
679        self._icon_index = (
680            self._spazzes.index(character) if character in self._spazzes else 0
681        )
682        self._update_character()

A character has been selected by the picker.

@override
def on_character_picker_get_more_press(self) -> None:
684    @override
685    def on_character_picker_get_more_press(self) -> None:
686        from bauiv1lib.store.browser import StoreBrowserWindow
687
688        if not self.main_window_has_control():
689            return
690
691        self.main_window_replace(
692            StoreBrowserWindow(
693                minimal_toolbars=True,
694                show_tab=StoreBrowserWindow.TabID.CHARACTERS,
695            )
696        )

Called when the 'get more characters' button is pressed.

def color_picker_closing(self, picker: bauiv1lib.colorpicker.ColorPicker) -> None:
756    def color_picker_closing(self, picker: ColorPicker) -> None:
757        """Called when a color picker is closing."""
758        if not self._root_widget:
759            return
760        tag = picker.get_tag()
761        if tag == 'color':
762            bui.containerwidget(
763                edit=self._root_widget, selected_child=self._color_button
764            )
765        elif tag == 'highlight':
766            bui.containerwidget(
767                edit=self._root_widget, selected_child=self._highlight_button
768            )
769        else:
770            print('color_picker_closing got unknown tag ' + str(tag))

Called when a color picker is closing.

def color_picker_selected_color( self, picker: bauiv1lib.colorpicker.ColorPicker, color: tuple[float, float, float]) -> None:
772    def color_picker_selected_color(
773        self, picker: ColorPicker, color: tuple[float, float, float]
774    ) -> None:
775        """Called when a color is selected in a color picker."""
776        if not self._root_widget:
777            return
778        tag = picker.get_tag()
779        if tag == 'color':
780            self._set_color(color)
781        elif tag == 'highlight':
782            self._set_highlight(color)
783        else:
784            print('color_picker_selected_color got unknown tag ' + str(tag))
785        self._update_character()

Called when a color is selected in a color picker.

def getname(self) -> str:
828    def getname(self) -> str:
829        """Return the current profile name value."""
830        if self._is_account_profile:
831            new_name = '__account__'
832        elif self._global:
833            new_name = self._name
834        else:
835            new_name = cast(str, bui.textwidget(query=self._text_field))
836        return new_name

Return the current profile name value.

def save(self, transition_out: bool = True) -> bool:
838    def save(self, transition_out: bool = True) -> bool:
839        """Save has been selected."""
840
841        # no-op if our underlying widget is dead or on its way out.
842        if not self._root_widget or self._root_widget.transitioning_out:
843            return False
844
845        plus = bui.app.plus
846        assert plus is not None
847
848        new_name = self.getname().strip()
849
850        if not new_name:
851            bui.screenmessage(bui.Lstr(resource='nameNotEmptyText'))
852            bui.getsound('error').play()
853            return False
854
855        # Make sure we're not renaming to another existing profile.
856        profiles: dict = bui.app.config.get('Player Profiles', {})
857        if self._existing_profile != new_name and new_name in profiles.keys():
858            bui.screenmessage(
859                bui.Lstr(resource='editProfileWindow.profileAlreadyExistsText')
860            )
861            bui.getsound('error').play()
862            return False
863
864        if transition_out:
865            bui.getsound('gunCocking').play()
866
867        # Delete old in case we're renaming.
868        if self._existing_profile and self._existing_profile != new_name:
869            plus.add_v1_account_transaction(
870                {
871                    'type': 'REMOVE_PLAYER_PROFILE',
872                    'name': self._existing_profile,
873                }
874            )
875
876            # Also lets be aware we're no longer global if we're taking
877            # a new name (will need to re-request it).
878            self._global = False
879
880        plus.add_v1_account_transaction(
881            {
882                'type': 'ADD_PLAYER_PROFILE',
883                'name': new_name,
884                'profile': {
885                    'character': self._spazzes[self._icon_index],
886                    'color': list(self._color),
887                    'global': self._global,
888                    'icon': self._icon,
889                    'highlight': list(self._highlight),
890                },
891            }
892        )
893
894        if transition_out:
895            plus.run_v1_account_transactions()
896            self.main_window_back()
897
898        return True

Save has been selected.