Source code for bauiv1lib.profile.edit

# 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 cast, override

from bauiv1lib.colorpicker import ColorPicker
from bauiv1lib.characterpicker import CharacterPickerDelegate
from bauiv1lib.iconpicker import IconPickerDelegate
import bauiv1 as bui
import bascenev1 as bs


[docs] class EditProfileWindow( bui.MainWindow, CharacterPickerDelegate, IconPickerDelegate ): """Window for editing a player profile."""
[docs] def reload_window(self) -> None: """Transitions out and recreates ourself.""" # no-op if we're not in control. if not self.main_window_has_control(): return # Replace ourself with ourself, but keep the same back location. assert self.main_window_back_state is not None self.main_window_replace( EditProfileWindow(self.getname()), back_state=self.main_window_back_state, )
# def __del__(self) -> None: # print(f'~EditProfileWindow({id(self)})') def __init__( self, existing_profile: str | None, transition: str | None = 'in_right', origin_widget: bui.Widget | None = None, ): # FIXME: Tidy this up a bit. # pylint: disable=too-many-branches # pylint: disable=too-many-statements # pylint: disable=too-many-locals assert bui.app.classic is not None # print(f'EditProfileWindow({id(self)})') plus = bui.app.plus assert plus is not None self._existing_profile = existing_profile self._r = 'editProfileWindow' self._spazzes: list[str] = [] self._icon_textures: list[bui.Texture] = [] self._icon_tint_textures: list[bui.Texture] = [] # Grab profile colors or pick random ones. ( self._color, self._highlight, ) = bui.app.classic.get_player_profile_colors(existing_profile) uiscale = bui.app.ui_v1.uiscale self._width = width = 880.0 if uiscale is bui.UIScale.SMALL else 680.0 self._x_inset = x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0 self._height = height = ( 500.0 if uiscale is bui.UIScale.SMALL else 400.0 if uiscale is bui.UIScale.MEDIUM else 450.0 ) yoffs = -42 if uiscale is bui.UIScale.SMALL else 0 spacing = 40 self._base_scale = ( 1.6 if uiscale is bui.UIScale.SMALL else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 ) top_extra = 70 if uiscale is bui.UIScale.SMALL else 15 super().__init__( root_widget=bui.containerwidget( size=(width, height + top_extra), scale=self._base_scale, stack_offset=( (0, 0) if uiscale is bui.UIScale.SMALL else (0, 0) ), toolbar_visibility=( None if uiscale is bui.UIScale.SMALL else 'menu_full' ), ), transition=transition, origin_widget=origin_widget, ) cancel_button = btn = bui.buttonwidget( parent=self._root_widget, position=(52 + x_inset, height - 60 + yoffs), size=(155, 60), scale=0.8, autoselect=True, label=bui.Lstr(resource='cancelText'), on_activate_call=self._cancel, ) bui.containerwidget(edit=self._root_widget, cancel_button=btn) save_button = btn = bui.buttonwidget( parent=self._root_widget, position=(width - (177 + x_inset), height - 60 + yoffs), size=(155, 60), autoselect=True, scale=0.8, label=bui.Lstr(resource='saveText'), ) bui.widget(edit=save_button, left_widget=cancel_button) bui.widget(edit=cancel_button, right_widget=save_button) bui.containerwidget(edit=self._root_widget, start_button=btn) bui.textwidget( parent=self._root_widget, position=(self._width * 0.5, height - 38 + yoffs), size=(0, 0), text=( bui.Lstr(resource=f'{self._r}.titleNewText') if existing_profile is None else bui.Lstr(resource=f'{self._r}.titleEditText') ), color=bui.app.ui_v1.title_color, maxwidth=290, scale=1.0, h_align='center', v_align='center', ) # Make a list of spaz icons. self.refresh_characters() profile = bui.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 = bui.charstr(bui.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(bui.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 bui.buttonwidget(edit=save_button, on_activate_call=self.save) v = height - 115.0 + yoffs 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: assert bui.app.classic is not None clr = bui.app.classic.spaz_appearances[ self._spazzes[icon_index] ].default_color if clr is not None: self._color = clr highlight = bui.app.classic.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 = bs.get_random_names() self._name = names[random.randrange(len(names))] self._clipped_name_text = bui.textwidget( parent=self._root_widget, text='', position=(580 + 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: bui.textwidget( parent=self._root_widget, text=bui.Lstr(resource=f'{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 plus.get_v1_account_state() == 'signed_in': sval = plus.get_v1_account_display_string() else: sval = '??' bui.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 = bui.Lstr( resource='editProfileWindow.accountProfileText' ).evaluate() b_width = min( 270.0, bui.get_string_width(txtl, suppress_warning=True) * 0.6, ) bui.textwidget( parent=self._root_widget, position=(self._width * 0.5, v - 39), size=(0, 0), scale=0.6, color=bui.app.ui_v1.infotextcolor, text=txtl, maxwidth=270, h_align='center', v_align='center', ) self._account_type_info_button = bui.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 = bui.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 = bui.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, ) bui.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=bui.Lstr(resource=f'{self._r}.iconText'), scale=0.7, color=bui.app.ui_v1.title_color, maxwidth=120, ) self._update_icon() bui.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 = bui.Lstr( resource='editProfileWindow.globalProfileText' ).evaluate() b_width = min( 240.0, bui.get_string_width(txtl, suppress_warning=True) * 0.6, ) bui.textwidget( parent=self._root_widget, position=(self._width * 0.5, v - 39), size=(0, 0), scale=0.6, color=bui.app.ui_v1.infotextcolor, text=txtl, maxwidth=240, h_align='center', v_align='center', ) self._account_type_info_button = bui.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 = bui.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=bui.Lstr(resource=f'{self._r}.nameDescriptionText'), autoselect=True, editable=True, padding=4, color=(0.9, 0.9, 0.9, 1.0), on_return_press_call=bui.Call(save_button.activate), ) # FIXME hard coded strings are bad txtl = bui.Lstr( resource='editProfileWindow.localProfileText' ).evaluate() b_width = min( 270.0, bui.get_string_width(txtl, suppress_warning=True) * 0.6, ) bui.textwidget( parent=self._root_widget, position=(self._width * 0.5, v - 43), size=(0, 0), scale=0.6, color=bui.app.ui_v1.infotextcolor, text=txtl, maxwidth=270, h_align='center', v_align='center', ) self._account_type_info_button = bui.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 = bui.buttonwidget( parent=self._root_widget, label=bui.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._random_name_button = bui.buttonwidget( parent=self._root_widget, label=bui.Lstr(resource='randomText'), size=(30, 20), position=(495 + x_inset, v - 20), button_type='square', color=(0.6, 0.5, 0.65), autoselect=True, on_activate_call=self.assign_random_name, ) self._update_clipped_name() self._clipped_name_timer = bui.AppTimer( 0.333, bui.WeakCall(self._update_clipped_name), repeat=True ) v -= spacing * 3.0 b_size = 80 b_size_2 = 100 b_offs = 150 self._color_button = btn = bui.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() bui.buttonwidget( edit=self._color_button, on_activate_call=bui.WeakCall(self._make_picker, 'color', origin), ) bui.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=bui.Lstr(resource=f'{self._r}.colorText'), scale=0.7, color=bui.app.ui_v1.title_color, maxwidth=120, ) self._character_button = btn = bui.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=bui.gettexture('characterIconMask'), ) if not self._is_account_profile and not self._global: bui.containerwidget( edit=self._root_widget, selected_child=self._text_field ) bui.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=bui.Lstr(resource=f'{self._r}.characterText'), scale=0.7, color=bui.app.ui_v1.title_color, maxwidth=130, ) self._highlight_button = btn = bui.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: bui.widget(edit=cancel_button, down_widget=self._text_field) bui.widget(edit=save_button, down_widget=self._text_field) bui.widget(edit=self._color_button, up_widget=self._text_field) bui.widget( edit=self._account_type_info_button, down_widget=self._character_button, ) origin = self._highlight_button.get_screen_space_center() bui.buttonwidget( edit=self._highlight_button, on_activate_call=bui.WeakCall( self._make_picker, 'highlight', origin ), ) bui.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=bui.Lstr(resource=f'{self._r}.highlightText'), scale=0.7, color=bui.app.ui_v1.title_color, maxwidth=120, ) self._update_character()
[docs] @override def get_main_window_state(self) -> bui.MainWindowState: # Support recreating our window for back/refresh purposes. cls = type(self) # Pull things out of self here; if we do it within the lambda # we'll keep ourself alive which is bad. existing_profile = self._existing_profile return bui.BasicMainWindowState( create_call=lambda transition, origin_widget: cls( transition=transition, origin_widget=origin_widget, existing_profile=existing_profile, ) )
[docs] def assign_random_name(self) -> None: """Assigning a random name to the player.""" names = bs.get_random_names() name = names[random.randrange(len(names))] bui.textwidget( edit=self._text_field, text=name, )
[docs] def upgrade_profile(self) -> None: """Attempt to upgrade the profile to global.""" from bauiv1lib import account from bauiv1lib.profile import upgrade as pupgrade new_name = self.getname().strip() if self._existing_profile and self._existing_profile != new_name: bui.screenmessage( 'Unsaved changes found; you must save first.', color=(1, 0, 0) ) bui.getsound('error').play() return plus = bui.app.plus assert plus is not None if plus.get_v1_account_state() != 'signed_in': account.show_sign_in_prompt() return pupgrade.ProfileUpgradeWindow(self)
[docs] def show_account_profile_info(self) -> None: """Show an explanation of account profiles.""" from bauiv1lib.confirm import ConfirmWindow icons_str = ' '.join( [ bui.charstr(n) for n in [ bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO, bui.SpecialChar.GAME_CENTER_LOGO, bui.SpecialChar.LOCAL_ACCOUNT, bui.SpecialChar.OCULUS_LOGO, bui.SpecialChar.NVIDIA_LOGO, bui.SpecialChar.V2_LOGO, ] ] ) txtl = bui.Lstr( resource='editProfileWindow.accountProfileInfoText', subs=[('${ICONS}', icons_str)], ) ConfirmWindow( txtl, cancel_button=False, width=500, height=300, origin_widget=self._account_type_info_button, )
[docs] def show_local_profile_info(self) -> None: """Show an explanation of local profiles.""" from bauiv1lib.confirm import ConfirmWindow txtl = bui.Lstr(resource='editProfileWindow.localProfileInfoText') ConfirmWindow( txtl, cancel_button=False, width=600, height=250, origin_widget=self._account_type_info_button, )
[docs] def show_global_profile_info(self) -> None: """Show an explanation of global profiles.""" from bauiv1lib.confirm import ConfirmWindow txtl = bui.Lstr(resource='editProfileWindow.globalProfileInfoText') ConfirmWindow( txtl, cancel_button=False, width=600, height=250, origin_widget=self._account_type_info_button, )
[docs] def refresh_characters(self) -> None: """Refresh available characters/icons.""" from bascenev1lib.actor import spazappearance assert bui.app.classic is not None self._spazzes = spazappearance.get_appearances() self._spazzes.sort() self._icon_textures = [ bui.gettexture(bui.app.classic.spaz_appearances[s].icon_texture) for s in self._spazzes ] self._icon_tint_textures = [ bui.gettexture( bui.app.classic.spaz_appearances[s].icon_mask_texture ) for s in self._spazzes ]
[docs] @override def on_icon_picker_pick(self, icon: str) -> None: """An icon has been selected by the picker.""" self._icon = icon self._update_icon()
[docs] @override def on_icon_picker_get_more_press(self) -> None: """User wants to get more icons.""" from bauiv1lib.store.browser import StoreBrowserWindow if not self.main_window_has_control(): return self.main_window_replace( StoreBrowserWindow( minimal_toolbars=True, show_tab=StoreBrowserWindow.TabID.ICONS, ) )
[docs] @override 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()
[docs] @override def on_character_picker_get_more_press(self) -> None: from bauiv1lib.store.browser import StoreBrowserWindow if not self.main_window_has_control(): return self.main_window_replace( StoreBrowserWindow( minimal_toolbars=True, show_tab=StoreBrowserWindow.TabID.CHARACTERS, ) )
def _on_character_press(self) -> None: from bauiv1lib 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 bauiv1lib 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: 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( 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: self.main_window_back() def _set_color(self, color: tuple[float, float, float]) -> None: self._color = color if self._color_button: bui.buttonwidget(edit=self._color_button, color=color) def _set_highlight(self, color: tuple[float, float, float]) -> None: self._highlight = color if self._highlight_button: bui.buttonwidget(edit=self._highlight_button, color=color)
[docs] 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': bui.containerwidget( edit=self._root_widget, selected_child=self._color_button ) elif tag == 'highlight': bui.containerwidget( edit=self._root_widget, selected_child=self._highlight_button ) else: print('color_picker_closing got unknown tag ' + str(tag))
[docs] 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: plus = bui.app.plus assert plus is not None if not self._clipped_name_text: return name = self.getname() if name == '__account__': name = ( plus.get_v1_account_name() if plus.get_v1_account_state() == 'signed_in' else '???' ) if len(name) > 10 and not (self._global or self._is_account_profile): name = name.strip() display_name = (name[:10] + '...') if len(name) > 10 else name bui.textwidget( edit=self._clipped_name_text, text=bui.Lstr( resource='inGameClippedNameText', subs=[('${NAME}', display_name)], ), ) else: bui.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: bui.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: bui.textwidget(edit=self._icon_button_label, text=self._icon)
[docs] 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, bui.textwidget(query=self._text_field)) return new_name
[docs] def save(self, transition_out: bool = True) -> bool: """Save has been selected.""" # no-op if our underlying widget is dead or on its way out. if not self._root_widget or self._root_widget.transitioning_out: return False plus = bui.app.plus assert plus is not None new_name = self.getname().strip() if not new_name: bui.screenmessage(bui.Lstr(resource='nameNotEmptyText')) bui.getsound('error').play() return False # Make sure we're not renaming to another existing profile. profiles: dict = bui.app.config.get('Player Profiles', {}) if self._existing_profile != new_name and new_name in profiles.keys(): bui.screenmessage( bui.Lstr(resource='editProfileWindow.profileAlreadyExistsText') ) bui.getsound('error').play() return False if transition_out: bui.getsound('gunCocking').play() # Delete old in case we're renaming. if self._existing_profile and self._existing_profile != new_name: plus.add_v1_account_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 plus.add_v1_account_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: plus.run_v1_account_transactions() self.main_window_back() return True