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

Window for editing a player profile.

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

Transitions out and recreates ourself.

@override
def get_main_window_state(self) -> bauiv1.MainWindowState:
522    @override
523    def get_main_window_state(self) -> bui.MainWindowState:
524        # Support recreating our window for back/refresh purposes.
525        cls = type(self)
526        return bui.BasicMainWindowState(
527            create_call=lambda transition, origin_widget: cls(
528                transition=transition,
529                origin_widget=origin_widget,
530                existing_profile=self._existing_profile,
531                # in_main_menu=self._in_main_menu,
532            )
533        )

Return a WindowState to recreate this window, if supported.

def assign_random_name(self) -> None:
535    def assign_random_name(self) -> None:
536        """Assigning a random name to the player."""
537        names = bs.get_random_names()
538        name = names[random.randrange(len(names))]
539        bui.textwidget(
540            edit=self._text_field,
541            text=name,
542        )

Assigning a random name to the player.

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

Attempt to upgrade the profile to global.

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

Show an explanation of account profiles.

def show_local_profile_info(self) -> None:
596    def show_local_profile_info(self) -> None:
597        """Show an explanation of local profiles."""
598        from bauiv1lib.confirm import ConfirmWindow
599
600        txtl = bui.Lstr(resource='editProfileWindow.localProfileInfoText')
601        ConfirmWindow(
602            txtl,
603            cancel_button=False,
604            width=600,
605            height=250,
606            origin_widget=self._account_type_info_button,
607        )

Show an explanation of local profiles.

def show_global_profile_info(self) -> None:
609    def show_global_profile_info(self) -> None:
610        """Show an explanation of global profiles."""
611        from bauiv1lib.confirm import ConfirmWindow
612
613        txtl = bui.Lstr(resource='editProfileWindow.globalProfileInfoText')
614        ConfirmWindow(
615            txtl,
616            cancel_button=False,
617            width=600,
618            height=250,
619            origin_widget=self._account_type_info_button,
620        )

Show an explanation of global profiles.

def refresh_characters(self) -> None:
622    def refresh_characters(self) -> None:
623        """Refresh available characters/icons."""
624        from bascenev1lib.actor import spazappearance
625
626        assert bui.app.classic is not None
627
628        self._spazzes = spazappearance.get_appearances()
629        self._spazzes.sort()
630        self._icon_textures = [
631            bui.gettexture(bui.app.classic.spaz_appearances[s].icon_texture)
632            for s in self._spazzes
633        ]
634        self._icon_tint_textures = [
635            bui.gettexture(
636                bui.app.classic.spaz_appearances[s].icon_mask_texture
637            )
638            for s in self._spazzes
639        ]

Refresh available characters/icons.

def on_icon_picker_pick(self, icon: str) -> None:
641    def on_icon_picker_pick(self, icon: str) -> None:
642        """An icon has been selected by the picker."""
643        self._icon = icon
644        self._update_icon()

An icon has been selected by the picker.

@override
def on_character_picker_pick(self, character: str) -> None:
646    @override
647    def on_character_picker_pick(self, character: str) -> None:
648        """A character has been selected by the picker."""
649        if not self._root_widget:
650            return
651
652        # The player could have bought a new one while the picker was up.
653        self.refresh_characters()
654        self._icon_index = (
655            self._spazzes.index(character) if character in self._spazzes else 0
656        )
657        self._update_character()

A character has been selected by the picker.

@override
def on_character_picker_get_more_press(self) -> None:
659    @override
660    def on_character_picker_get_more_press(self) -> None:
661        from bauiv1lib.store.browser import StoreBrowserWindow
662
663        if not self.main_window_has_control():
664            return
665
666        self.main_window_replace(
667            StoreBrowserWindow(
668                minimal_toolbars=True,
669                show_tab=StoreBrowserWindow.TabID.CHARACTERS,
670            )
671        )

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

def color_picker_closing(self, picker: bauiv1lib.colorpicker.ColorPicker) -> None:
731    def color_picker_closing(self, picker: ColorPicker) -> None:
732        """Called when a color picker is closing."""
733        if not self._root_widget:
734            return
735        tag = picker.get_tag()
736        if tag == 'color':
737            bui.containerwidget(
738                edit=self._root_widget, selected_child=self._color_button
739            )
740        elif tag == 'highlight':
741            bui.containerwidget(
742                edit=self._root_widget, selected_child=self._highlight_button
743            )
744        else:
745            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:
747    def color_picker_selected_color(
748        self, picker: ColorPicker, color: tuple[float, float, float]
749    ) -> None:
750        """Called when a color is selected in a color picker."""
751        if not self._root_widget:
752            return
753        tag = picker.get_tag()
754        if tag == 'color':
755            self._set_color(color)
756        elif tag == 'highlight':
757            self._set_highlight(color)
758        else:
759            print('color_picker_selected_color got unknown tag ' + str(tag))
760        self._update_character()

Called when a color is selected in a color picker.

def getname(self) -> str:
803    def getname(self) -> str:
804        """Return the current profile name value."""
805        if self._is_account_profile:
806            new_name = '__account__'
807        elif self._global:
808            new_name = self._name
809        else:
810            new_name = cast(str, bui.textwidget(query=self._text_field))
811        return new_name

Return the current profile name value.

def save(self, transition_out: bool = True) -> bool:
813    def save(self, transition_out: bool = True) -> bool:
814        """Save has been selected."""
815
816        # no-op if our underlying widget is dead or on its way out.
817        if not self._root_widget or self._root_widget.transitioning_out:
818            return False
819
820        plus = bui.app.plus
821        assert plus is not None
822
823        new_name = self.getname().strip()
824
825        if not new_name:
826            bui.screenmessage(bui.Lstr(resource='nameNotEmptyText'))
827            bui.getsound('error').play()
828            return False
829
830        # Make sure we're not renaming to another existing profile.
831        profiles: dict = bui.app.config.get('Player Profiles', {})
832        if self._existing_profile != new_name and new_name in profiles.keys():
833            bui.screenmessage(
834                bui.Lstr(resource='editProfileWindow.profileAlreadyExistsText')
835            )
836            bui.getsound('error').play()
837            return False
838
839        if transition_out:
840            bui.getsound('gunCocking').play()
841
842        # Delete old in case we're renaming.
843        if self._existing_profile and self._existing_profile != new_name:
844            plus.add_v1_account_transaction(
845                {
846                    'type': 'REMOVE_PLAYER_PROFILE',
847                    'name': self._existing_profile,
848                }
849            )
850
851            # Also lets be aware we're no longer global if we're taking a
852            # new name (will need to re-request it).
853            self._global = False
854
855        plus.add_v1_account_transaction(
856            {
857                'type': 'ADD_PLAYER_PROFILE',
858                'name': new_name,
859                'profile': {
860                    'character': self._spazzes[self._icon_index],
861                    'color': list(self._color),
862                    'global': self._global,
863                    'icon': self._icon,
864                    'highlight': list(self._highlight),
865                },
866            }
867        )
868
869        if transition_out:
870            plus.run_v1_account_transactions()
871            self.main_window_back()
872
873        return True

Save has been selected.

Inherited Members
bauiv1._uitypes.MainWindow
main_window_back_state
main_window_is_top_level
main_window_close
main_window_has_control
main_window_back
main_window_replace
on_main_window_close
bauiv1._uitypes.Window
get_root_widget