# Released under the MIT License. See LICENSE for details. # """Provides UI to edit a player profile.""" from __future__ import annotations import random from typing import TYPE_CHECKING, cast import ba import ba.internal if TYPE_CHECKING: from bastd.ui.colorpicker import ColorPicker class EditProfileWindow(ba.Window): """Window for editing a player profile.""" # FIXME: WILL NEED TO CHANGE THIS FOR UILOCATION. def reload_window(self) -> None: """Transitions out and recreates ourself.""" ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( EditProfileWindow( self.getname(), self._in_main_menu ).get_root_widget() ) def __init__( self, existing_profile: str | None, in_main_menu: bool, transition: str = 'in_right', ): # FIXME: Tidy this up a bit. # pylint: disable=too-many-branches # pylint: disable=too-many-statements # pylint: disable=too-many-locals from ba.internal import get_player_profile_colors self._in_main_menu = in_main_menu self._existing_profile = existing_profile self._r = 'editProfileWindow' self._spazzes: list[str] = [] self._icon_textures: list[ba.Texture] = [] self._icon_tint_textures: list[ba.Texture] = [] # Grab profile colors or pick random ones. self._color, self._highlight = get_player_profile_colors( existing_profile ) uiscale = ba.app.ui.uiscale self._width = width = 780.0 if uiscale is ba.UIScale.SMALL else 680.0 self._x_inset = x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0 self._height = height = ( 350.0 if uiscale is ba.UIScale.SMALL else 400.0 if uiscale is ba.UIScale.MEDIUM else 450.0 ) spacing = 40 self._base_scale = ( 2.05 if uiscale is ba.UIScale.SMALL else 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0 ) top_extra = 15 if uiscale is ba.UIScale.SMALL else 15 super().__init__( root_widget=ba.containerwidget( size=(width, height + top_extra), transition=transition, scale=self._base_scale, stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0), ) ) cancel_button = btn = ba.buttonwidget( parent=self._root_widget, position=(52 + x_inset, height - 60), size=(155, 60), scale=0.8, autoselect=True, label=ba.Lstr(resource='cancelText'), on_activate_call=self._cancel, ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) save_button = btn = ba.buttonwidget( parent=self._root_widget, position=(width - (177 + x_inset), height - 60), size=(155, 60), autoselect=True, scale=0.8, label=ba.Lstr(resource='saveText'), ) ba.widget(edit=save_button, left_widget=cancel_button) ba.widget(edit=cancel_button, right_widget=save_button) ba.containerwidget(edit=self._root_widget, start_button=btn) ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, height - 38), size=(0, 0), text=( ba.Lstr(resource=self._r + '.titleNewText') if existing_profile is None else ba.Lstr(resource=self._r + '.titleEditText') ), color=ba.app.ui.title_color, maxwidth=290, scale=1.0, h_align='center', v_align='center', ) # Make a list of spaz icons. self.refresh_characters() profile = ba.app.config.get('Player Profiles', {}).get( self._existing_profile, {} ) if 'global' in profile: self._global = profile['global'] else: self._global = False if 'icon' in profile: self._icon = profile['icon'] else: self._icon = ba.charstr(ba.SpecialChar.LOGO) assigned_random_char = False # Look for existing character choice or pick random one otherwise. try: icon_index = self._spazzes.index(profile['character']) except Exception: # Let's set the default icon to spaz for our first profile; after # that we go random. # (SCRATCH THAT.. we now hard-code account-profiles to start with # spaz which has a similar effect) # try: p_len = len(ba.app.config['Player Profiles']) # except Exception: p_len = 0 # if p_len == 0: icon_index = self._spazzes.index('Spaz') # else: random.seed() icon_index = random.randrange(len(self._spazzes)) assigned_random_char = True self._icon_index = icon_index ba.buttonwidget(edit=save_button, on_activate_call=self.save) v = height - 115.0 self._name = ( '' if self._existing_profile is None else self._existing_profile ) self._is_account_profile = self._name == '__account__' # If we just picked a random character, see if it has specific # colors/highlights associated with it and assign them if so. if assigned_random_char: clr = ba.app.spaz_appearances[ self._spazzes[icon_index] ].default_color if clr is not None: self._color = clr highlight = ba.app.spaz_appearances[ self._spazzes[icon_index] ].default_highlight if highlight is not None: self._highlight = highlight # Assign a random name if they had none. if self._name == '': names = ba.internal.get_random_names() self._name = names[random.randrange(len(names))] self._clipped_name_text = ba.textwidget( parent=self._root_widget, text='', position=(540 + x_inset, v - 8), flatness=1.0, shadow=0.0, scale=0.55, size=(0, 0), maxwidth=100, h_align='center', v_align='center', color=(1, 1, 0, 0.5), ) if not self._is_account_profile and not self._global: ba.textwidget( parent=self._root_widget, text=ba.Lstr(resource=self._r + '.nameText'), position=(200 + x_inset, v - 6), size=(0, 0), h_align='right', v_align='center', color=(1, 1, 1, 0.5), scale=0.9, ) self._upgrade_button = None if self._is_account_profile: if ba.internal.get_v1_account_state() == 'signed_in': sval = ba.internal.get_v1_account_display_string() else: sval = '??' ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, v - 7), size=(0, 0), scale=1.2, text=sval, maxwidth=270, h_align='center', v_align='center', ) txtl = ba.Lstr( resource='editProfileWindow.accountProfileText' ).evaluate() b_width = min( 270.0, ba.internal.get_string_width(txtl, suppress_warning=True) * 0.6, ) ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, v - 39), size=(0, 0), scale=0.6, color=ba.app.ui.infotextcolor, text=txtl, maxwidth=270, h_align='center', v_align='center', ) self._account_type_info_button = ba.buttonwidget( parent=self._root_widget, label='?', size=(15, 15), text_scale=0.6, position=(self._width * 0.5 + b_width * 0.5 + 13, v - 47), button_type='square', color=(0.6, 0.5, 0.65), autoselect=True, on_activate_call=self.show_account_profile_info, ) elif self._global: b_size = 60 self._icon_button = btn = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(self._width * 0.5 - 160 - b_size * 0.5, v - 38 - 15), size=(b_size, b_size), color=(0.6, 0.5, 0.6), label='', button_type='square', text_scale=1.2, on_activate_call=self._on_icon_press, ) self._icon_button_label = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5 - 160, v - 35), draw_controller=btn, h_align='center', v_align='center', size=(0, 0), color=(1, 1, 1), text='', scale=2.0, ) ba.textwidget( parent=self._root_widget, h_align='center', v_align='center', position=(self._width * 0.5 - 160, v - 55 - 15), size=(0, 0), draw_controller=btn, text=ba.Lstr(resource=self._r + '.iconText'), scale=0.7, color=ba.app.ui.title_color, maxwidth=120, ) self._update_icon() ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, v - 7), size=(0, 0), scale=1.2, text=self._name, maxwidth=240, h_align='center', v_align='center', ) # FIXME hard coded strings are bad txtl = ba.Lstr( resource='editProfileWindow.globalProfileText' ).evaluate() b_width = min( 240.0, ba.internal.get_string_width(txtl, suppress_warning=True) * 0.6, ) ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, v - 39), size=(0, 0), scale=0.6, color=ba.app.ui.infotextcolor, text=txtl, maxwidth=240, h_align='center', v_align='center', ) self._account_type_info_button = ba.buttonwidget( parent=self._root_widget, label='?', size=(15, 15), text_scale=0.6, position=(self._width * 0.5 + b_width * 0.5 + 13, v - 47), button_type='square', color=(0.6, 0.5, 0.65), autoselect=True, on_activate_call=self.show_global_profile_info, ) else: self._text_field = ba.textwidget( parent=self._root_widget, position=(220 + x_inset, v - 30), size=(265, 40), text=self._name, h_align='left', v_align='center', max_chars=16, description=ba.Lstr(resource=self._r + '.nameDescriptionText'), autoselect=True, editable=True, padding=4, color=(0.9, 0.9, 0.9, 1.0), on_return_press_call=ba.Call(save_button.activate), ) # FIXME hard coded strings are bad txtl = ba.Lstr( resource='editProfileWindow.localProfileText' ).evaluate() b_width = min( 270.0, ba.internal.get_string_width(txtl, suppress_warning=True) * 0.6, ) ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, v - 43), size=(0, 0), scale=0.6, color=ba.app.ui.infotextcolor, text=txtl, maxwidth=270, h_align='center', v_align='center', ) self._account_type_info_button = ba.buttonwidget( parent=self._root_widget, label='?', size=(15, 15), text_scale=0.6, position=(self._width * 0.5 + b_width * 0.5 + 13, v - 50), button_type='square', color=(0.6, 0.5, 0.65), autoselect=True, on_activate_call=self.show_local_profile_info, ) self._upgrade_button = ba.buttonwidget( parent=self._root_widget, label=ba.Lstr(resource='upgradeText'), size=(40, 17), text_scale=1.0, button_type='square', position=(self._width * 0.5 + b_width * 0.5 + 13 + 43, v - 51), color=(0.6, 0.5, 0.65), autoselect=True, on_activate_call=self.upgrade_profile, ) self._update_clipped_name() self._clipped_name_timer = ba.Timer( 0.333, ba.WeakCall(self._update_clipped_name), timetype=ba.TimeType.REAL, repeat=True, ) v -= spacing * 3.0 b_size = 80 b_size_2 = 100 b_offs = 150 self._color_button = btn = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(self._width * 0.5 - b_offs - b_size * 0.5, v - 50), size=(b_size, b_size), color=self._color, label='', button_type='square', ) origin = self._color_button.get_screen_space_center() ba.buttonwidget( edit=self._color_button, on_activate_call=ba.WeakCall(self._make_picker, 'color', origin), ) ba.textwidget( parent=self._root_widget, h_align='center', v_align='center', position=(self._width * 0.5 - b_offs, v - 65), size=(0, 0), draw_controller=btn, text=ba.Lstr(resource=self._r + '.colorText'), scale=0.7, color=ba.app.ui.title_color, maxwidth=120, ) self._character_button = btn = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(self._width * 0.5 - b_size_2 * 0.5, v - 60), up_widget=self._account_type_info_button, on_activate_call=self._on_character_press, size=(b_size_2, b_size_2), label='', color=(1, 1, 1), mask_texture=ba.gettexture('characterIconMask'), ) if not self._is_account_profile and not self._global: ba.containerwidget( edit=self._root_widget, selected_child=self._text_field ) ba.textwidget( parent=self._root_widget, h_align='center', v_align='center', position=(self._width * 0.5, v - 80), size=(0, 0), draw_controller=btn, text=ba.Lstr(resource=self._r + '.characterText'), scale=0.7, color=ba.app.ui.title_color, maxwidth=130, ) self._highlight_button = btn = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(self._width * 0.5 + b_offs - b_size * 0.5, v - 50), up_widget=self._upgrade_button if self._upgrade_button is not None else self._account_type_info_button, size=(b_size, b_size), color=self._highlight, label='', button_type='square', ) if not self._is_account_profile and not self._global: ba.widget(edit=cancel_button, down_widget=self._text_field) ba.widget(edit=save_button, down_widget=self._text_field) ba.widget(edit=self._color_button, up_widget=self._text_field) ba.widget( edit=self._account_type_info_button, down_widget=self._character_button, ) origin = self._highlight_button.get_screen_space_center() ba.buttonwidget( edit=self._highlight_button, on_activate_call=ba.WeakCall( self._make_picker, 'highlight', origin ), ) ba.textwidget( parent=self._root_widget, h_align='center', v_align='center', position=(self._width * 0.5 + b_offs, v - 65), size=(0, 0), draw_controller=btn, text=ba.Lstr(resource=self._r + '.highlightText'), scale=0.7, color=ba.app.ui.title_color, maxwidth=120, ) self._update_character() def upgrade_profile(self) -> None: """Attempt to ugrade the profile to global.""" from bastd.ui import account from bastd.ui.profile import upgrade as pupgrade if ba.internal.get_v1_account_state() != 'signed_in': account.show_sign_in_prompt() return pupgrade.ProfileUpgradeWindow(self) def show_account_profile_info(self) -> None: """Show an explanation of account profiles.""" from bastd.ui.confirm import ConfirmWindow icons_str = ' '.join( [ ba.charstr(n) for n in [ ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO, ba.SpecialChar.GAME_CENTER_LOGO, ba.SpecialChar.GAME_CIRCLE_LOGO, ba.SpecialChar.OUYA_LOGO, ba.SpecialChar.LOCAL_ACCOUNT, ba.SpecialChar.ALIBABA_LOGO, ba.SpecialChar.OCULUS_LOGO, ba.SpecialChar.NVIDIA_LOGO, ] ] ) txtl = ba.Lstr( resource='editProfileWindow.accountProfileInfoText', subs=[('${ICONS}', icons_str)], ) ConfirmWindow( txtl, cancel_button=False, width=500, height=300, origin_widget=self._account_type_info_button, ) def show_local_profile_info(self) -> None: """Show an explanation of local profiles.""" from bastd.ui.confirm import ConfirmWindow txtl = ba.Lstr(resource='editProfileWindow.localProfileInfoText') ConfirmWindow( txtl, cancel_button=False, width=600, height=250, origin_widget=self._account_type_info_button, ) def show_global_profile_info(self) -> None: """Show an explanation of global profiles.""" from bastd.ui.confirm import ConfirmWindow txtl = ba.Lstr(resource='editProfileWindow.globalProfileInfoText') ConfirmWindow( txtl, cancel_button=False, width=600, height=250, origin_widget=self._account_type_info_button, ) def refresh_characters(self) -> None: """Refresh available characters/icons.""" from bastd.actor import spazappearance self._spazzes = spazappearance.get_appearances() self._spazzes.sort() self._icon_textures = [ ba.gettexture(ba.app.spaz_appearances[s].icon_texture) for s in self._spazzes ] self._icon_tint_textures = [ ba.gettexture(ba.app.spaz_appearances[s].icon_mask_texture) for s in self._spazzes ] def on_icon_picker_pick(self, icon: str) -> None: """An icon has been selected by the picker.""" self._icon = icon self._update_icon() def on_character_picker_pick(self, character: str) -> None: """A character has been selected by the picker.""" if not self._root_widget: return # The player could have bought a new one while the picker was up. self.refresh_characters() self._icon_index = ( self._spazzes.index(character) if character in self._spazzes else 0 ) self._update_character() def _on_character_press(self) -> None: from bastd.ui import characterpicker characterpicker.CharacterPicker( parent=self._root_widget, position=self._character_button.get_screen_space_center(), selected_character=self._spazzes[self._icon_index], delegate=self, tint_color=self._color, tint2_color=self._highlight, ) def _on_icon_press(self) -> None: from bastd.ui import iconpicker iconpicker.IconPicker( parent=self._root_widget, position=self._icon_button.get_screen_space_center(), selected_icon=self._icon, delegate=self, tint_color=self._color, tint2_color=self._highlight, ) def _make_picker( self, picker_type: str, origin: tuple[float, float] ) -> None: from bastd.ui import colorpicker if picker_type == 'color': initial_color = self._color elif picker_type == 'highlight': initial_color = self._highlight else: raise ValueError('invalid picker_type: ' + picker_type) colorpicker.ColorPicker( parent=self._root_widget, position=origin, offset=( self._base_scale * (-100 if picker_type == 'color' else 100), 0, ), initial_color=initial_color, delegate=self, tag=picker_type, ) def _cancel(self) -> None: from bastd.ui.profile.browser import ProfileBrowserWindow ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( ProfileBrowserWindow( 'in_left', selected_profile=self._existing_profile, in_main_menu=self._in_main_menu, ).get_root_widget() ) def _set_color(self, color: tuple[float, float, float]) -> None: self._color = color if self._color_button: ba.buttonwidget(edit=self._color_button, color=color) def _set_highlight(self, color: tuple[float, float, float]) -> None: self._highlight = color if self._highlight_button: ba.buttonwidget(edit=self._highlight_button, color=color) def color_picker_closing(self, picker: ColorPicker) -> None: """Called when a color picker is closing.""" if not self._root_widget: return tag = picker.get_tag() if tag == 'color': ba.containerwidget( edit=self._root_widget, selected_child=self._color_button ) elif tag == 'highlight': ba.containerwidget( edit=self._root_widget, selected_child=self._highlight_button ) else: print('color_picker_closing got unknown tag ' + str(tag)) def color_picker_selected_color( self, picker: ColorPicker, color: tuple[float, float, float] ) -> None: """Called when a color is selected in a color picker.""" if not self._root_widget: return tag = picker.get_tag() if tag == 'color': self._set_color(color) elif tag == 'highlight': self._set_highlight(color) else: print('color_picker_selected_color got unknown tag ' + str(tag)) self._update_character() def _update_clipped_name(self) -> None: if not self._clipped_name_text: return name = self.getname() if name == '__account__': name = ( ba.internal.get_v1_account_name() if ba.internal.get_v1_account_state() == 'signed_in' else '???' ) if len(name) > 10 and not (self._global or self._is_account_profile): ba.textwidget( edit=self._clipped_name_text, text=ba.Lstr( resource='inGameClippedNameText', subs=[('${NAME}', name[:10] + '...')], ), ) else: ba.textwidget(edit=self._clipped_name_text, text='') def _update_character(self, change: int = 0) -> None: self._icon_index = (self._icon_index + change) % len(self._spazzes) if self._character_button: ba.buttonwidget( edit=self._character_button, texture=self._icon_textures[self._icon_index], tint_texture=self._icon_tint_textures[self._icon_index], tint_color=self._color, tint2_color=self._highlight, ) def _update_icon(self) -> None: if self._icon_button_label: ba.textwidget(edit=self._icon_button_label, text=self._icon) def getname(self) -> str: """Return the current profile name value.""" if self._is_account_profile: new_name = '__account__' elif self._global: new_name = self._name else: new_name = cast(str, ba.textwidget(query=self._text_field)) return new_name def save(self, transition_out: bool = True) -> bool: """Save has been selected.""" from bastd.ui.profile.browser import ProfileBrowserWindow new_name = self.getname().strip() if not new_name: ba.screenmessage(ba.Lstr(resource='nameNotEmptyText')) ba.playsound(ba.getsound('error')) return False if transition_out: ba.playsound(ba.getsound('gunCocking')) # Delete old in case we're renaming. if self._existing_profile and self._existing_profile != new_name: ba.internal.add_transaction( { 'type': 'REMOVE_PLAYER_PROFILE', 'name': self._existing_profile, } ) # Also lets be aware we're no longer global if we're taking a # new name (will need to re-request it). self._global = False ba.internal.add_transaction( { 'type': 'ADD_PLAYER_PROFILE', 'name': new_name, 'profile': { 'character': self._spazzes[self._icon_index], 'color': list(self._color), 'global': self._global, 'icon': self._icon, 'highlight': list(self._highlight), }, } ) if transition_out: ba.internal.run_transactions() ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( ProfileBrowserWindow( 'in_left', selected_profile=new_name, in_main_menu=self._in_main_menu, ).get_root_widget() ) return True