def __init__(self, transition: str = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # pylint: disable=too-many-locals # pylint: disable=cyclic-import from bastd.ui.popup import PopupMenu from bastd.ui.config import ConfigNumberEdit music = ba.app.music # If they provided an origin-widget, scale up from that. scale_origin: Optional[Tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None self._r = 'audioSettingsWindow' spacing = 50.0 width = 460.0 height = 210.0 # Update: hard-coding head-relative audio to true now, # so not showing options. # show_vr_head_relative_audio = True if ba.app.vr_mode else False show_vr_head_relative_audio = False if show_vr_head_relative_audio: height += 70 show_soundtracks = False if music.have_music_player(): show_soundtracks = True height += spacing * 2.0 base_scale = (2.05 if ba.app.small_ui else 1.6 if ba.app.med_ui else 1.0) popup_menu_scale = base_scale * 1.2 super().__init__(root_widget=ba.containerwidget( size=(width, height), transition=transition, scale=base_scale, scale_origin_stack_offset=scale_origin, stack_offset=(0, -20) if ba.app.small_ui else (0, 0))) self._back_button = back_button = btn = ba.buttonwidget( parent=self._root_widget, position=(35, height - 55), size=(120, 60), scale=0.8, text_scale=1.2, label=ba.Lstr(resource='backText'), button_type='back', on_activate_call=self._back, autoselect=True) ba.containerwidget(edit=self._root_widget, cancel_button=btn) v = height - 60 v -= spacing * 1.0 ba.textwidget(parent=self._root_widget, position=(width * 0.5, height - 32), size=(0, 0), text=ba.Lstr(resource=self._r + '.titleText'), color=ba.app.title_color, maxwidth=180, h_align='center', v_align='center') ba.buttonwidget(edit=self._back_button, button_type='backSmall', size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) self._sound_volume_numedit = svne = ConfigNumberEdit( parent=self._root_widget, position=(40, v), xoffset=10, configkey='Sound Volume', displayname=ba.Lstr(resource=self._r + '.soundVolumeText'), minval=0.0, maxval=1.0, increment=0.1) if ba.app.toolbars: ba.widget(edit=svne.plusbutton, right_widget=_ba.get_special_widget('party_button')) v -= spacing self._music_volume_numedit = ConfigNumberEdit( parent=self._root_widget, position=(40, v), xoffset=10, configkey='Music Volume', displayname=ba.Lstr(resource=self._r + '.musicVolumeText'), minval=0.0, maxval=1.0, increment=0.1, callback=music.music_volume_changed, changesound=False) v -= 0.5 * spacing self._vr_head_relative_audio_button: Optional[ba.Widget] if show_vr_head_relative_audio: v -= 40 ba.textwidget(parent=self._root_widget, position=(40, v + 24), size=(0, 0), text=ba.Lstr(resource=self._r + '.headRelativeVRAudioText'), color=(0.8, 0.8, 0.8), maxwidth=230, h_align='left', v_align='center') popup = PopupMenu( parent=self._root_widget, position=(290, v), width=120, button_size=(135, 50), scale=popup_menu_scale, choices=['Auto', 'On', 'Off'], choices_display=[ ba.Lstr(resource='autoText'), ba.Lstr(resource='onText'), ba.Lstr(resource='offText') ], current_choice=ba.app.config.resolve('VR Head Relative Audio'), on_value_change_call=self._set_vr_head_relative_audio) self._vr_head_relative_audio_button = popup.get_button() ba.textwidget(parent=self._root_widget, position=(width * 0.5, v - 11), size=(0, 0), text=ba.Lstr(resource=self._r + '.headRelativeVRAudioInfoText'), scale=0.5, color=(0.7, 0.8, 0.7), maxwidth=400, flatness=1.0, h_align='center', v_align='center') v -= 30 else: self._vr_head_relative_audio_button = None self._soundtrack_button: Optional[ba.Widget] if show_soundtracks: v -= 1.2 * spacing self._soundtrack_button = ba.buttonwidget( parent=self._root_widget, position=((width - 310) / 2, v), size=(310, 50), autoselect=True, label=ba.Lstr(resource=self._r + '.soundtrackButtonText'), on_activate_call=self._do_soundtracks) v -= spacing * 0.5 ba.textwidget(parent=self._root_widget, position=(0, v), size=(width, 20), text=ba.Lstr(resource=self._r + '.soundtrackDescriptionText'), flatness=1.0, h_align='center', scale=0.5, color=(0.7, 0.8, 0.7, 1.0), maxwidth=400) else: self._soundtrack_button = None # tweak a few navigation bits try: ba.widget(edit=back_button, down_widget=svne.minusbutton) except Exception: ba.print_exception('error wiring AudioSettingsWindow') self._restore_state()
def __init__(self, origin_widget: ba.Widget = None): scale_origin: Optional[Tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None transition = 'in_right' bg_color = (0.4, 0.4, 0.5) self._width = 540 self._height = 350 self._scroll_width = 400 self._scroll_height = 200 uiscale = ba.app.ui.uiscale base_scale = (2.0 if uiscale is ba.UIScale.SMALL else 1.6 if uiscale is ba.UIScale.MEDIUM else 1.1) super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height), transition=transition, scale=base_scale, scale_origin_stack_offset=scale_origin, stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0))) self._cancel_button = ba.buttonwidget(parent=self._root_widget, position=(30, self._height - 50), size=(50, 50), scale=0.7, label='', color=bg_color, on_activate_call=self._cancel, autoselect=True, icon=ba.gettexture('crossOut'), iconscale=1.2) ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height * 0.88), size=(0, 0), text=ba.Lstr( resource='accountSettingsWindow.unlinkAccountsInstructionsText' ), maxwidth=self._width * 0.7, color=ba.app.ui.infotextcolor, h_align='center', v_align='center') ba.containerwidget(edit=self._root_widget, cancel_button=self._cancel_button) self._scrollwidget = ba.scrollwidget( parent=self._root_widget, highlight=False, position=((self._width - self._scroll_width) * 0.5, self._height - 85 - self._scroll_height), size=(self._scroll_width, self._scroll_height)) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) self._columnwidget = ba.columnwidget(parent=self._scrollwidget, border=2, margin=0, left_border=10) our_login_id = _ba.get_public_login_id() if our_login_id is None: entries = [] else: account_infos = _ba.get_account_misc_read_val_2( 'linkedAccounts2', []) entries = [{ 'name': ai['d'], 'id': ai['id'] } for ai in account_infos if ai['id'] != our_login_id] # (avoid getting our selection stuck on an empty column widget) if not entries: ba.containerwidget(edit=self._scrollwidget, selectable=False) for i, entry in enumerate(entries): txt = ba.textwidget(parent=self._columnwidget, selectable=True, text=entry['name'], size=(self._scroll_width - 30, 30), autoselect=True, click_activate=True, on_activate_call=ba.Call( self._on_entry_selected, entry)) ba.widget(edit=txt, left_widget=self._cancel_button) if i == 0: ba.widget(edit=txt, up_widget=self._cancel_button)
def _on_max_public_party_size_plus_press(self) -> None: val = _ba.get_public_party_max_size() val += 1 _ba.set_public_party_max_size(val) ba.textwidget(edit=self._host_max_party_size_value, text=str(val))
def __init__(self, transition: str = 'in_right', modal: bool = False, origin_widget: ba.Widget = None, close_once_signed_in: bool = False): # pylint: disable=too-many-statements self._close_once_signed_in = close_once_signed_in ba.set_analytics_screen('Account Window') # If they provided an origin-widget, scale up from that. scale_origin: Optional[Tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None self._r = 'accountSettingsWindow' self._modal = modal self._needs_refresh = False self._signed_in = (_ba.get_account_state() == 'signed_in') self._account_state_num = _ba.get_account_state_num() self._show_linked = (self._signed_in and _ba.get_account_misc_read_val( 'allowAccountLinking2', False)) self._check_sign_in_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True) # Currently we can only reset achievements on game-center. account_type: Optional[str] if self._signed_in: account_type = _ba.get_account_type() else: account_type = None self._can_reset_achievements = (account_type == 'Game Center') app = ba.app uiscale = app.uiscale self._width = 760 if uiscale is ba.UIScale.SMALL else 660 x_offs = 50 if uiscale is ba.UIScale.SMALL else 0 self._height = (390 if uiscale is ba.UIScale.SMALL else 430 if uiscale is ba.UIScale.MEDIUM else 490) self._sign_in_button = None self._sign_in_text = None self._scroll_width = self._width - (100 + x_offs * 2) self._scroll_height = self._height - 120 self._sub_width = self._scroll_width - 20 # Determine which sign-in/sign-out buttons we should show. self._show_sign_in_buttons: List[str] = [] if app.platform == 'android' and app.subplatform == 'google': self._show_sign_in_buttons.append('Google Play') elif app.platform == 'android' and app.subplatform == 'amazon': self._show_sign_in_buttons.append('Game Circle') # Local accounts are generally always available with a few key # exceptions. self._show_sign_in_buttons.append('Local') top_extra = 15 if uiscale is ba.UIScale.SMALL else 0 super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height + top_extra), transition=transition, toolbar_visibility='menu_minimal', scale_origin_stack_offset=scale_origin, scale=(2.09 if uiscale is ba.UIScale.SMALL else 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -19) if uiscale is ba.UIScale.SMALL else (0, 0))) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: self._back_button = None ba.containerwidget(edit=self._root_widget, on_cancel_call=self._back) else: self._back_button = btn = ba.buttonwidget( parent=self._root_widget, position=(51 + x_offs, self._height - 62), size=(120, 60), scale=0.8, text_scale=1.2, autoselect=True, label=ba.Lstr( resource='doneText' if self._modal else 'backText'), button_type='regular' if self._modal else 'back', on_activate_call=self._back) ba.containerwidget(edit=self._root_widget, cancel_button=btn) if not self._modal: ba.buttonwidget(edit=btn, button_type='backSmall', size=(60, 56), label=ba.charstr(ba.SpecialChar.BACK)) ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height - 41), size=(0, 0), text=ba.Lstr(resource=self._r + '.titleText'), color=ba.app.ui.title_color, maxwidth=self._width - 340, h_align='center', v_align='center') self._scrollwidget = ba.scrollwidget( parent=self._root_widget, highlight=False, position=((self._width - self._scroll_width) * 0.5, self._height - 65 - self._scroll_height), size=(self._scroll_width, self._scroll_height)) self._subcontainer: Optional[ba.Widget] = None self._refresh() self._restore_state()
def _stop_flashing(self) -> None: self._flashing_timer = None ba.textwidget(edit=self._title_text, color=(0.3, 1, 0.3))
def _refresh(self, select_soundtrack: str = None) -> None: self._allow_changing_soundtracks = False old_selection = self._selected_soundtrack # If there was no prev selection, look in prefs. if old_selection is None: try: old_selection = ba.app.config['Soundtrack'] except Exception: pass old_selection_index = self._selected_soundtrack_index # Delete old. while self._soundtrack_widgets: self._soundtrack_widgets.pop().delete() try: self._soundtracks = ba.app.config['Soundtracks'] except Exception: self._soundtracks = {} assert self._soundtracks is not None items = list(self._soundtracks.items()) items.sort(key=lambda x: x[0].lower()) items = [('__default__', None)] + items # default is always first index = 0 for pname, _pval in items: assert pname is not None txtw = ba.textwidget( parent=self._col, size=(self._width - 40, 24), text=self._get_soundtrack_display_name(pname), h_align='left', v_align='center', maxwidth=self._width - 110, always_highlight=True, on_select_call=ba.WeakCall(self._select, pname, index), on_activate_call=self._edit_soundtrack_with_sound, selectable=True) if index == 0: ba.widget(edit=txtw, up_widget=self._back_button) self._soundtrack_widgets.append(txtw) # Select this one if the user requested it if select_soundtrack is not None: if pname == select_soundtrack: ba.columnwidget(edit=self._col, selected_child=txtw, visible_child=txtw) else: # Select this one if it was previously selected. # Go by index if there's one. if old_selection_index is not None: if index == old_selection_index: ba.columnwidget(edit=self._col, selected_child=txtw, visible_child=txtw) else: # Otherwise look by name. if pname == old_selection: ba.columnwidget(edit=self._col, selected_child=txtw, visible_child=txtw) index += 1 # Explicitly run select callback on current one and re-enable # callbacks. # Eww need to run this in a timer so it happens after our select # callbacks. With a small-enough time sometimes it happens before # anyway. Ew. need a way to just schedule a callable i guess. ba.timer(0.1, ba.WeakCall(self._set_allow_changing), timetype=ba.TimeType.REAL)
def __init__(self, path: str, callback: Callable[[Optional[str]], Any] = None, show_base_path: bool = True, valid_file_extensions: Sequence[str] = None, allow_folders: bool = False): if valid_file_extensions is None: valid_file_extensions = [] uiscale = ba.app.ui.uiscale self._width = 700 if uiscale is ba.UIScale.SMALL else 600 self._x_inset = x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 self._height = 365 if uiscale is ba.UIScale.SMALL else 418 self._callback = callback self._base_path = path self._path: Optional[str] = None self._recent_paths: List[str] = [] self._show_base_path = show_base_path self._valid_file_extensions = [ '.' + ext for ext in valid_file_extensions ] self._allow_folders = allow_folders self._subcontainer: Optional[ba.Widget] = None self._subcontainerheight: Optional[float] = None self._scroll_width = self._width - (80 + 2 * x_inset) self._scroll_height = self._height - 170 self._r = 'fileSelectorWindow' super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height), transition='in_right', scale=(2.23 if uiscale is ba.UIScale.SMALL else 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -35) if uiscale is ba.UIScale.SMALL else (0, 0))) ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height - 42), size=(0, 0), color=ba.app.ui.title_color, h_align='center', v_align='center', text=ba.Lstr(resource=self._r + '.titleFolderText') if (allow_folders and not valid_file_extensions) else ba.Lstr( resource=self._r + '.titleFileText') if not allow_folders else ba.Lstr( resource=self._r + '.titleFileFolderText'), maxwidth=210) self._button_width = 146 self._cancel_button = ba.buttonwidget( parent=self._root_widget, position=(35 + x_inset, self._height - 67), autoselect=True, size=(self._button_width, 50), label=ba.Lstr(resource='cancelText'), on_activate_call=self._cancel) ba.widget(edit=self._cancel_button, left_widget=self._cancel_button) b_color = (0.6, 0.53, 0.63) self._back_button = ba.buttonwidget( parent=self._root_widget, button_type='square', position=(43 + x_inset, self._height - 113), color=b_color, textcolor=(0.75, 0.7, 0.8), enable_sound=False, size=(55, 35), label=ba.charstr(ba.SpecialChar.LEFT_ARROW), on_activate_call=self._on_back_press) self._folder_tex = ba.gettexture('folder') self._folder_color = (1.1, 0.8, 0.2) self._file_tex = ba.gettexture('file') self._file_color = (1, 1, 1) self._use_folder_button: Optional[ba.Widget] = None self._folder_center = self._width * 0.5 + 15 self._folder_icon = ba.imagewidget(parent=self._root_widget, size=(40, 40), position=(40, self._height - 117), texture=self._folder_tex, color=self._folder_color) self._path_text = ba.textwidget(parent=self._root_widget, position=(self._folder_center, self._height - 98), size=(0, 0), color=ba.app.ui.title_color, h_align='center', v_align='center', text=self._path, maxwidth=self._width * 0.9) self._scrollwidget: Optional[ba.Widget] = None ba.containerwidget(edit=self._root_widget, cancel_button=self._cancel_button) self._set_path(path)
def on_activate( self, parent_widget: ba.Widget, tab_button: ba.Widget, region_width: float, region_height: float, region_left: float, region_bottom: float, ) -> ba.Widget: self._c_width = region_width self._c_height = region_height - 20 self._container = ba.containerwidget( parent=parent_widget, position=(region_left, region_bottom + (region_height - self._c_height) * 0.5), size=(self._c_width, self._c_height), background=False, selection_loops_to_parent=True) v = self._c_height - 30.0 self._join_sub_tab_text = ba.textwidget( parent=self._container, position=(self._c_width * 0.5 - 245, v - 13), color=(0.6, 1.0, 0.6), scale=1.3, size=(200, 30), maxwidth=250, h_align='left', v_align='center', click_activate=True, selectable=True, autoselect=True, on_activate_call=lambda: self._set_sub_tab( SubTabType.JOIN, playsound=True, ), text=ba.Lstr(resource='gatherWindow.privatePartyJoinText')) self._host_sub_tab_text = ba.textwidget( parent=self._container, position=(self._c_width * 0.5 + 45, v - 13), color=(0.6, 1.0, 0.6), scale=1.3, size=(200, 30), maxwidth=250, h_align='left', v_align='center', click_activate=True, selectable=True, autoselect=True, on_activate_call=lambda: self._set_sub_tab( SubTabType.HOST, playsound=True, ), text=ba.Lstr(resource='gatherWindow.privatePartyHostText')) ba.widget(edit=self._join_sub_tab_text, up_widget=tab_button) ba.widget(edit=self._host_sub_tab_text, left_widget=self._join_sub_tab_text, up_widget=tab_button) ba.widget(edit=self._join_sub_tab_text, right_widget=self._host_sub_tab_text) self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), repeat=True, timetype=ba.TimeType.REAL) # Prevent taking any action until we've updated our state. self._waiting_for_initial_state = True # This will get a state query sent out immediately. self._last_action_send_time = None # Ensure we don't ignore response. self._last_hosting_state_query_time = None self._update() self._set_sub_tab(self._state.sub_tab) return self._container
def _refresh(self) -> None: # pylint: disable=too-many-locals from ba.internal import (PlayerProfilesChangedMessage, get_player_profile_colors, get_player_profile_icon) old_selection = self._selected_profile # Delete old. while self._profile_widgets: self._profile_widgets.pop().delete() self._profiles = ba.app.config.get('Player Profiles', {}) assert self._profiles is not None items = list(self._profiles.items()) items.sort(key=lambda x: x[0].lower()) index = 0 account_name: Optional[str] if _ba.get_account_state() == 'signed_in': account_name = _ba.get_account_display_string() else: account_name = None widget_to_select = None for p_name, _ in items: if p_name == '__account__' and account_name is None: continue color, _highlight = get_player_profile_colors(p_name) scl = 1.1 tval = (account_name if p_name == '__account__' else get_player_profile_icon(p_name) + p_name) assert isinstance(tval, str) txtw = ba.textwidget( parent=self._columnwidget, position=(0, 32), size=((self._width - 40) / scl, 28), text=ba.Lstr(value=tval), h_align='left', v_align='center', on_select_call=ba.WeakCall(self._select, p_name, index), maxwidth=self._scroll_width * 0.92, corner_scale=scl, color=ba.safecolor(color, 0.4), always_highlight=True, on_activate_call=ba.Call(self._edit_button.activate), selectable=True) if index == 0: ba.widget(edit=txtw, up_widget=self._back_button) ba.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40) self._profile_widgets.append(txtw) # Select/show this one if it was previously selected # (but defer till after this loop since our height is # still changing). if p_name == old_selection: widget_to_select = txtw index += 1 if widget_to_select is not None: ba.columnwidget(edit=self._columnwidget, selected_child=widget_to_select, visible_child=widget_to_select) # If there's a team-chooser in existence, tell it the profile-list # has probably changed. session = _ba.get_foreground_host_session() if session is not None: session.handlemessage(PlayerProfilesChangedMessage())
def __init__(self, origin_widget: ba.Widget = None): # pylint: disable=too-many-locals # pylint: disable=too-many-statements import json ba.set_analytics_screen('Credits Window') # if they provided an origin-widget, scale up from that scale_origin: Optional[Tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None transition = 'in_right' uiscale = ba.app.uiscale width = 870 if uiscale is ba.UIScale.SMALL else 670 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 height = 398 if uiscale is ba.UIScale.SMALL else 500 self._r = 'creditsWindow' super().__init__(root_widget=ba.containerwidget( size=(width, height), transition=transition, toolbar_visibility='menu_minimal', scale_origin_stack_offset=scale_origin, scale=(2.0 if uiscale is ba.UIScale.SMALL else 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0))) if ba.app.toolbars and uiscale is ba.UIScale.SMALL: ba.containerwidget(edit=self._root_widget, on_cancel_call=self._back) else: btn = ba.buttonwidget( parent=self._root_widget, position=(40 + x_inset, height - (68 if uiscale is ba.UIScale.SMALL else 62)), size=(140, 60), scale=0.8, label=ba.Lstr(resource='backText'), button_type='back', on_activate_call=self._back, autoselect=True) ba.containerwidget(edit=self._root_widget, cancel_button=btn) ba.buttonwidget( edit=btn, button_type='backSmall', position=(40 + x_inset, height - (68 if uiscale is ba.UIScale.SMALL else 62) + 5), size=(60, 48), label=ba.charstr(ba.SpecialChar.BACK)) ba.textwidget(parent=self._root_widget, position=(0, height - (59 if uiscale is ba.UIScale.SMALL else 54)), size=(width, 30), text=ba.Lstr(resource=self._r + '.titleText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))]), h_align='center', color=ba.app.title_color, maxwidth=330, v_align='center') scroll = ba.scrollwidget(parent=self._root_widget, position=(40 + x_inset, 35), size=(width - (80 + 2 * x_inset), height - 100), capture_arrows=True) if ba.app.toolbars: ba.widget(edit=scroll, right_widget=_ba.get_special_widget('party_button')) if uiscale is ba.UIScale.SMALL: ba.widget(edit=scroll, left_widget=_ba.get_special_widget('back_button')) def _format_names(names2: Sequence[str], inset: float) -> str: sval = '' # measure a series since there's overlaps and stuff.. space_width = _ba.get_string_width(' ' * 10, suppress_warning=True) / 10.0 spacing = 330.0 col1 = inset col2 = col1 + spacing col3 = col2 + spacing line_width = 0.0 nline = '' for name in names2: # move to the next column (or row) and print if line_width > col3: sval += nline + '\n' nline = '' line_width = 0 if line_width > col2: target = col3 elif line_width > col1: target = col2 else: target = col1 spacingstr = ' ' * int((target - line_width) / space_width) nline += spacingstr nline += name line_width = _ba.get_string_width(nline, suppress_warning=True) if nline != '': sval += nline + '\n' return sval sound_and_music = ba.Lstr(resource=self._r + '.songCreditText').evaluate() sound_and_music = sound_and_music.replace( '${TITLE}', "'William Tell (Trumpet Entry)'") sound_and_music = sound_and_music.replace( '${PERFORMER}', 'The Apollo Symphony Orchestra') sound_and_music = sound_and_music.replace( '${PERFORMER}', 'The Apollo Symphony Orchestra') sound_and_music = sound_and_music.replace('${COMPOSER}', 'Gioacchino Rossini') sound_and_music = sound_and_music.replace('${ARRANGER}', 'Chris Worth') sound_and_music = sound_and_music.replace('${PUBLISHER}', 'BMI') sound_and_music = sound_and_music.replace('${SOURCE}', 'www.AudioSparx.com') spc = ' ' sound_and_music = spc + sound_and_music.replace('\n', '\n' + spc) names = [ 'HubOfTheUniverseProd', 'Jovica', 'LG', 'Leady', 'Percy Duke', 'PhreaKsAccount', 'Pogotron', 'Rock Savage', 'anamorphosis', 'benboncan', 'cdrk', 'chipfork', 'guitarguy1985', 'jascha', 'joedeshon', 'loofa', 'm_O_m', 'mich3d', 'sandyrb', 'shakaharu', 'sirplus', 'stickman', 'thanvannispen', 'virotic', 'zimbot' ] names.sort(key=lambda x: x.lower()) freesound_names = _format_names(names, 90) try: with open('ba_data/data/langdata.json') as infile: translation_contributors = (json.loads( infile.read())['translation_contributors']) except Exception: ba.print_exception('Error reading translation contributors.') translation_contributors = [] translation_names = _format_names(translation_contributors, 60) # Need to bake this out and chop it up since we're passing our # 65535 vertex limit for meshes.. # We can remove that limit once we drop support for GL ES2.. :-/ # (or add mesh splitting under the hood) credits_text = ( ' ' + ba.Lstr(resource=self._r + '.codingGraphicsAudioText').evaluate().replace( '${NAME}', 'Eric Froemling') + '\n' '\n' ' ' + ba.Lstr(resource=self._r + '.additionalAudioArtIdeasText').evaluate().replace( '${NAME}', 'Raphael Suter') + '\n' '\n' ' ' + ba.Lstr(resource=self._r + '.soundAndMusicText').evaluate() + '\n' '\n' + sound_and_music + '\n' '\n' ' ' + ba.Lstr(resource=self._r + '.publicDomainMusicViaText').evaluate().replace( '${NAME}', 'Musopen.com') + '\n' ' ' + ba.Lstr(resource=self._r + '.thanksEspeciallyToText').evaluate().replace( '${NAME}', 'the US Army, Navy, and Marine Bands') + '\n' '\n' ' ' + ba.Lstr(resource=self._r + '.additionalMusicFromText').evaluate().replace( '${NAME}', 'The YouTube Audio Library') + '\n' '\n' ' ' + ba.Lstr(resource=self._r + '.soundsText').evaluate().replace( '${SOURCE}', 'Freesound.org') + '\n' '\n' + freesound_names + '\n' '\n' ' ' + ba.Lstr(resource=self._r + '.languageTranslationsText').evaluate() + '\n' '\n' + '\n'.join(translation_names.splitlines()[:146]) + '\n'.join(translation_names.splitlines()[146:]) + '\n' '\n' ' Shout Out to Awesome Mods / Modders:\n\n' ' BombDash ModPack\n' ' TheMikirog & SoK - BombSquad Joyride Modpack\n' ' Mrmaxmeier - BombSquad-Community-Mod-Manager\n' '\n' ' Holiday theme vector art designed by Freepik\n' '\n' ' ' + ba.Lstr(resource=self._r + '.specialThanksText').evaluate() + '\n' '\n' ' Todd, Laura, and Robert Froemling\n' ' ' + ba.Lstr(resource=self._r + '.allMyFamilyText').evaluate().replace( '\n', '\n ') + '\n' ' ' + ba.Lstr(resource=self._r + '.whoeverInventedCoffeeText').evaluate() + '\n' '\n' ' ' + ba.Lstr(resource=self._r + '.legalText').evaluate() + '\n' '\n' ' ' + ba.Lstr(resource=self._r + '.softwareBasedOnText').evaluate().replace( '${NAME}', 'the Khronos Group') + '\n' '\n' ' ' ' www.froemling.net\n') txt = credits_text lines = txt.splitlines() line_height = 20 scale = 0.55 self._sub_width = width - 80 self._sub_height = line_height * len(lines) + 40 container = self._subcontainer = ba.containerwidget( parent=scroll, size=(self._sub_width, self._sub_height), background=False, claims_left_right=False, claims_tab=False) voffs = 0 for line in lines: ba.textwidget(parent=container, padding=4, color=(0.7, 0.9, 0.7, 1.0), scale=scale, flatness=1.0, size=(0, 0), position=(0, self._sub_height - 20 + voffs), h_align='left', v_align='top', text=ba.Lstr(value=line)) voffs -= line_height
def _build_host_tab(self) -> None: # pylint: disable=too-many-branches # pylint: disable=too-many-statements if _ba.get_v1_account_state() != 'signed_in': ba.textwidget(parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=200, scale=0.8, color=(0.6, 0.56, 0.6), position=(self._c_width * 0.5, self._c_height * 0.5), text=ba.Lstr(resource='notSignedInErrorText')) self._showing_not_signed_in_screen = True return self._showing_not_signed_in_screen = False # At first we don't want to show anything until we've gotten a state. # Update: In this situation we now simply show our existing state # but give the start/stop button a loading message and disallow its # use. This keeps things a lot less jumpy looking and allows selecting # playlists/etc without having to wait for the server each time # back to the ui. if self._waiting_for_initial_state and bool(False): ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=200, scale=0.8, color=(0.6, 0.56, 0.6), position=(self._c_width * 0.5, self._c_height * 0.5), text=ba.Lstr( value='${A}...', subs=[('${A}', ba.Lstr(resource='store.loadingText'))], ), ) return # If we're not currently hosting and hosting requires tickets, # Show our count (possibly with a link to purchase more). if (not self._waiting_for_initial_state and self._hostingstate.party_code is None and self._hostingstate.tickets_to_host_now != 0): if not ba.app.ui.use_toolbars: if ba.app.allow_ticket_purchases: self._get_tickets_button = ba.buttonwidget( parent=self._container, position=(self._c_width - 210 + 125, self._c_height - 44), autoselect=True, scale=0.6, size=(120, 60), textcolor=(0.2, 1, 0.2), label=ba.charstr(ba.SpecialChar.TICKET), color=(0.65, 0.5, 0.8), on_activate_call=self._on_get_tickets_press) else: self._ticket_count_text = ba.textwidget( parent=self._container, scale=0.6, position=(self._c_width - 210 + 125, self._c_height - 44), color=(0.2, 1, 0.2), h_align='center', v_align='center') # Set initial ticket count. self._update_currency_ui() v = self._c_height - 90 if self._hostingstate.party_code is None: ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=(0.5, 0.46, 0.5), position=(self._c_width * 0.5, v), text=ba.Lstr( resource='gatherWindow.privatePartyCloudDescriptionText')) v -= 100 if self._hostingstate.party_code is None: # We've got no current party running; show options to set one up. ba.textwidget(parent=self._container, size=(0, 0), h_align='right', v_align='center', maxwidth=200, scale=0.8, color=(0.6, 0.56, 0.6), position=(self._c_width * 0.5 - 210, v), text=ba.Lstr(resource='playlistText')) self._host_playlist_button = ba.buttonwidget( parent=self._container, size=(400, 70), color=(0.6, 0.5, 0.6), textcolor=(0.8, 0.75, 0.8), label=self._hostingconfig.playlist_name, on_activate_call=self._playlist_press, position=(self._c_width * 0.5 - 200, v - 35), up_widget=self._host_sub_tab_text, autoselect=True) # If it appears we're coming back from playlist selection, # re-select our playlist button. if ba.app.ui.selecting_private_party_playlist: ba.containerwidget(edit=self._container, selected_child=self._host_playlist_button) ba.app.ui.selecting_private_party_playlist = False else: # We've got a current party; show its info. ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=600, scale=0.9, color=(0.7, 0.64, 0.7), position=(self._c_width * 0.5, v + 90), text=ba.Lstr(resource='gatherWindow.partyServerRunningText')) ba.textwidget(parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=600, scale=0.7, color=(0.7, 0.64, 0.7), position=(self._c_width * 0.5, v + 50), text=ba.Lstr(resource='gatherWindow.partyCodeText')) ba.textwidget(parent=self._container, size=(0, 0), h_align='center', v_align='center', scale=2.0, color=(0.0, 1.0, 0.0), position=(self._c_width * 0.5, v + 10), text=self._hostingstate.party_code) # Also action buttons to copy it and connect to it. if ba.clipboard_is_supported(): cbtnoffs = 10 self._host_copy_button = ba.buttonwidget( parent=self._container, size=(140, 40), color=(0.6, 0.5, 0.6), textcolor=(0.8, 0.75, 0.8), label=ba.Lstr(resource='gatherWindow.copyCodeText'), on_activate_call=self._host_copy_press, position=(self._c_width * 0.5 - 150, v - 70), autoselect=True) else: cbtnoffs = -70 self._host_connect_button = ba.buttonwidget( parent=self._container, size=(140, 40), color=(0.6, 0.5, 0.6), textcolor=(0.8, 0.75, 0.8), label=ba.Lstr(resource='gatherWindow.manualConnectText'), on_activate_call=self._host_connect_press, position=(self._c_width * 0.5 + cbtnoffs, v - 70), autoselect=True) v -= 120 # Line above the main action button: # If we don't want to show anything until we get a state: if self._waiting_for_initial_state: pass elif self._hostingstate.unavailable_error is not None: # If hosting is unavailable, show the associated reason. ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=(1.0, 0.0, 0.0), position=(self._c_width * 0.5, v), text=ba.Lstr(translate=('serverResponses', self._hostingstate.unavailable_error))) elif self._hostingstate.free_host_minutes_remaining is not None: # If we've been pre-approved to start/stop for free, show that. ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=((0.7, 0.64, 0.7) if self._hostingstate.party_code else (0.0, 1.0, 0.0)), position=(self._c_width * 0.5, v), text=ba.Lstr( resource='gatherWindow.startStopHostingMinutesText', subs=[( '${MINUTES}', f'{self._hostingstate.free_host_minutes_remaining:.0f}' )])) else: # Otherwise tell whether the free cloud server is available # or will be at some point. if self._hostingstate.party_code is None: if self._hostingstate.tickets_to_host_now == 0: ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=(0.0, 1.0, 0.0), position=(self._c_width * 0.5, v), text=ba.Lstr( resource= 'gatherWindow.freeCloudServerAvailableNowText')) else: if self._hostingstate.minutes_until_free_host is None: ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=(1.0, 0.6, 0.0), position=(self._c_width * 0.5, v), text=ba.Lstr( resource= 'gatherWindow.freeCloudServerNotAvailableText') ) else: availmins = self._hostingstate.minutes_until_free_host ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=(1.0, 0.6, 0.0), position=(self._c_width * 0.5, v), text=ba.Lstr(resource='gatherWindow.' 'freeCloudServerAvailableMinutesText', subs=[('${MINUTES}', f'{availmins:.0f}')])) v -= 100 if (self._waiting_for_start_stop_response or self._waiting_for_initial_state): btnlabel = ba.Lstr(resource='oneMomentText') else: if self._hostingstate.unavailable_error is not None: btnlabel = ba.Lstr( resource='gatherWindow.hostingUnavailableText') elif self._hostingstate.party_code is None: ticon = _ba.charstr(ba.SpecialChar.TICKET) nowtickets = self._hostingstate.tickets_to_host_now if nowtickets > 0: btnlabel = ba.Lstr( resource='gatherWindow.startHostingPaidText', subs=[('${COST}', f'{ticon}{nowtickets}')]) else: btnlabel = ba.Lstr( resource='gatherWindow.startHostingText') else: btnlabel = ba.Lstr(resource='gatherWindow.stopHostingText') disabled = (self._hostingstate.unavailable_error is not None or self._waiting_for_initial_state) waiting = self._waiting_for_start_stop_response self._host_start_stop_button = ba.buttonwidget( parent=self._container, size=(400, 80), color=((0.6, 0.6, 0.6) if disabled else (0.5, 1.0, 0.5) if waiting else None), enable_sound=False, label=btnlabel, textcolor=((0.7, 0.7, 0.7) if disabled else None), position=(self._c_width * 0.5 - 200, v), on_activate_call=self._start_stop_button_press, autoselect=True)
def __init__(self) -> None: from typing import cast width = 480 height = 170 spacing = 40 self._r = 'configGamepadSelectWindow' super().__init__(root_widget=ba.containerwidget( scale=2.3 if ba.app.small_ui else 1.5 if ba.app.med_ui else 1.0, size=(width, height), transition='in_right')) btn = ba.buttonwidget(parent=self._root_widget, position=(20, height - 60), size=(130, 60), label=ba.Lstr(resource='backText'), button_type='back', scale=0.8, on_activate_call=self._back) # Let's not have anything selected by default; its misleading looking # for the controller getting configured. ba.containerwidget(edit=self._root_widget, cancel_button=btn, selected_child=cast(ba.Widget, 0)) ba.textwidget(parent=self._root_widget, position=(20, height - 50), size=(width, 25), text=ba.Lstr(resource=self._r + '.titleText'), maxwidth=250, color=ba.app.title_color, h_align="center", v_align="center") ba.buttonwidget(edit=btn, button_type='backSmall', size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) v: float = height - 60 v -= spacing ba.textwidget(parent=self._root_widget, position=(15, v), size=(width - 30, 30), scale=0.8, text=ba.Lstr(resource=self._r + '.pressAnyButtonText'), maxwidth=width * 0.95, color=ba.app.infotextcolor, h_align="center", v_align="top") v -= spacing * 1.24 if ba.app.platform == 'android': ba.textwidget(parent=self._root_widget, position=(15, v), size=(width - 30, 30), scale=0.46, text=ba.Lstr(resource=self._r + '.androidNoteText'), maxwidth=width * 0.95, color=(0.7, 0.9, 0.7, 0.5), h_align="center", v_align="top") _ba.capture_gamepad_input(gamepad_configure_callback)
def __init__(self, transition: str = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-locals # pylint: disable=too-many-branches # pylint: disable=too-many-statements from bastd.ui import popup from bastd.ui.config import ConfigCheckBox, ConfigNumberEdit # if they provided an origin-widget, scale up from that scale_origin: Optional[Tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None self._r = 'graphicsSettingsWindow' app = ba.app spacing = 32 self._have_selected_child = False uiscale = app.ui.uiscale width = 450.0 height = 302.0 self._show_fullscreen = False fullscreen_spacing_top = spacing * 0.2 fullscreen_spacing = spacing * 1.2 if uiscale == ba.UIScale.LARGE and app.platform != 'android': self._show_fullscreen = True height += fullscreen_spacing + fullscreen_spacing_top show_gamma = False gamma_spacing = spacing * 1.3 if _ba.has_gamma_control(): show_gamma = True height += gamma_spacing show_vsync = False if app.platform == 'mac': show_vsync = True show_resolution = True if app.vr_mode: show_resolution = (app.platform == 'android' and app.subplatform == 'cardboard') uiscale = ba.app.ui.uiscale base_scale = (2.4 if uiscale is ba.UIScale.SMALL else 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0) popup_menu_scale = base_scale * 1.2 v = height - 50 v -= spacing * 1.15 super().__init__(root_widget=ba.containerwidget( size=(width, height), transition=transition, scale_origin_stack_offset=scale_origin, scale=base_scale, stack_offset=(0, -30) if uiscale is ba.UIScale.SMALL else (0, 0))) btn = ba.buttonwidget(parent=self._root_widget, position=(35, height - 50), size=(120, 60), scale=0.8, text_scale=1.2, autoselect=True, label=ba.Lstr(resource='backText'), button_type='back', on_activate_call=self._back) ba.containerwidget(edit=self._root_widget, cancel_button=btn) ba.textwidget(parent=self._root_widget, position=(0, height - 44), size=(width, 25), text=ba.Lstr(resource=self._r + '.titleText'), color=ba.app.ui.title_color, h_align='center', v_align='top') ba.buttonwidget(edit=btn, button_type='backSmall', size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) self._fullscreen_checkbox: Optional[ba.Widget] if self._show_fullscreen: v -= fullscreen_spacing_top self._fullscreen_checkbox = ConfigCheckBox( parent=self._root_widget, position=(100, v), maxwidth=200, size=(300, 30), configkey='Fullscreen', displayname=ba.Lstr(resource=self._r + ('.fullScreenCmdText' if app.platform == 'mac' else '.fullScreenCtrlText'))).widget if not self._have_selected_child: ba.containerwidget(edit=self._root_widget, selected_child=self._fullscreen_checkbox) self._have_selected_child = True v -= fullscreen_spacing else: self._fullscreen_checkbox = None self._gamma_controls: Optional[ConfigNumberEdit] if show_gamma: self._gamma_controls = gmc = ConfigNumberEdit( parent=self._root_widget, position=(90, v), configkey='Screen Gamma', displayname=ba.Lstr(resource=self._r + '.gammaText'), minval=0.1, maxval=2.0, increment=0.1, xoffset=-70, textscale=0.85) if ba.app.ui.use_toolbars: ba.widget(edit=gmc.plusbutton, right_widget=_ba.get_special_widget('party_button')) if not self._have_selected_child: ba.containerwidget(edit=self._root_widget, selected_child=gmc.minusbutton) self._have_selected_child = True v -= gamma_spacing else: self._gamma_controls = None self._selected_color = (0.5, 1, 0.5, 1) self._unselected_color = (0.7, 0.7, 0.7, 1) # quality ba.textwidget(parent=self._root_widget, position=(60, v), size=(160, 25), text=ba.Lstr(resource=self._r + '.visualsText'), color=ba.app.ui.heading_color, scale=0.65, maxwidth=150, h_align='center', v_align='center') popup.PopupMenu( parent=self._root_widget, position=(60, v - 50), width=150, scale=popup_menu_scale, choices=['Auto', 'Higher', 'High', 'Medium', 'Low'], choices_disabled=['Higher', 'High'] if _ba.get_max_graphics_quality() == 'Medium' else [], choices_display=[ ba.Lstr(resource='autoText'), ba.Lstr(resource=self._r + '.higherText'), ba.Lstr(resource=self._r + '.highText'), ba.Lstr(resource=self._r + '.mediumText'), ba.Lstr(resource=self._r + '.lowText') ], current_choice=ba.app.config.resolve('Graphics Quality'), on_value_change_call=self._set_quality) # texture controls ba.textwidget(parent=self._root_widget, position=(230, v), size=(160, 25), text=ba.Lstr(resource=self._r + '.texturesText'), color=ba.app.ui.heading_color, scale=0.65, maxwidth=150, h_align='center', v_align='center') textures_popup = popup.PopupMenu( parent=self._root_widget, position=(230, v - 50), width=150, scale=popup_menu_scale, choices=['Auto', 'High', 'Medium', 'Low'], choices_display=[ ba.Lstr(resource='autoText'), ba.Lstr(resource=self._r + '.highText'), ba.Lstr(resource=self._r + '.mediumText'), ba.Lstr(resource=self._r + '.lowText') ], current_choice=ba.app.config.resolve('Texture Quality'), on_value_change_call=self._set_textures) if ba.app.ui.use_toolbars: ba.widget(edit=textures_popup.get_button(), right_widget=_ba.get_special_widget('party_button')) v -= 80 h_offs = 0 if show_resolution: # resolution ba.textwidget(parent=self._root_widget, position=(h_offs + 60, v), size=(160, 25), text=ba.Lstr(resource=self._r + '.resolutionText'), color=ba.app.ui.heading_color, scale=0.65, maxwidth=150, h_align='center', v_align='center') # on standard android we have 'Auto', 'Native', and a few # HD standards if app.platform == 'android': # on cardboard/daydream android we have a few # render-target-scale options if app.subplatform == 'cardboard': current_res_cardboard = (str(min(100, max(10, int(round( ba.app.config.resolve('GVR Render Target Scale') * 100.0))))) + '%') # yapf: disable popup.PopupMenu( parent=self._root_widget, position=(h_offs + 60, v - 50), width=120, scale=popup_menu_scale, choices=['100%', '75%', '50%', '35%'], current_choice=current_res_cardboard, on_value_change_call=self._set_gvr_render_target_scale) else: native_res = _ba.get_display_resolution() assert native_res is not None choices = ['Auto', 'Native'] choices_display = [ ba.Lstr(resource='autoText'), ba.Lstr(resource='nativeText') ] for res in [1440, 1080, 960, 720, 480]: # nav bar is 72px so lets allow for that in what # choices we show if native_res[1] >= res - 72: res_str = str(res) + 'p' choices.append(res_str) choices_display.append(ba.Lstr(value=res_str)) current_res_android = ba.app.config.resolve( 'Resolution (Android)') popup.PopupMenu(parent=self._root_widget, position=(h_offs + 60, v - 50), width=120, scale=popup_menu_scale, choices=choices, choices_display=choices_display, current_choice=current_res_android, on_value_change_call=self._set_android_res) else: # if we're on a system that doesn't allow setting resolution, # set pixel-scale instead current_res = _ba.get_display_resolution() if current_res is None: current_res2 = (str(min(100, max(10, int(round( ba.app.config.resolve('Screen Pixel Scale') * 100.0))))) + '%') # yapf: disable popup.PopupMenu( parent=self._root_widget, position=(h_offs + 60, v - 50), width=120, scale=popup_menu_scale, choices=['100%', '88%', '75%', '63%', '50%'], current_choice=current_res2, on_value_change_call=self._set_pixel_scale) else: raise Exception('obsolete path; discrete resolutions' ' no longer supported') # vsync if show_vsync: ba.textwidget(parent=self._root_widget, position=(230, v), size=(160, 25), text=ba.Lstr(resource=self._r + '.verticalSyncText'), color=ba.app.ui.heading_color, scale=0.65, maxwidth=150, h_align='center', v_align='center') popup.PopupMenu( parent=self._root_widget, position=(230, v - 50), width=150, scale=popup_menu_scale, choices=['Auto', 'Always', 'Never'], choices_display=[ ba.Lstr(resource='autoText'), ba.Lstr(resource=self._r + '.alwaysText'), ba.Lstr(resource=self._r + '.neverText') ], current_choice=ba.app.config.resolve('Vertical Sync'), on_value_change_call=self._set_vsync) v -= 90 fpsc = ConfigCheckBox(parent=self._root_widget, position=(69, v - 6), size=(210, 30), scale=0.86, configkey='Show FPS', displayname=ba.Lstr(resource=self._r + '.showFPSText'), maxwidth=130) # (tv mode doesnt apply to vr) if not ba.app.vr_mode: tvc = ConfigCheckBox(parent=self._root_widget, position=(240, v - 6), size=(210, 30), scale=0.86, configkey='TV Border', displayname=ba.Lstr(resource=self._r + '.tvBorderText'), maxwidth=130) # grumble.. ba.widget(edit=fpsc.widget, right_widget=tvc.widget) try: pass except Exception: ba.print_exception('Exception wiring up graphics settings UI:') v -= spacing # make a timer to update our controls in case the config changes # under us self._update_timer = ba.Timer(0.25, ba.WeakCall(self._update_controls), repeat=True, timetype=ba.TimeType.REAL)
def _update_internet_tab(self) -> None: global searchText, textModified def updatepartylist(): self._internet_join_last_refresh_time = now self._first_public_party_list_rebuild_time = ba.time( ba.TimeType.REAL) + 1 app = ba.app _ba.add_transaction( { 'type': 'PUBLIC_PARTY_QUERY', 'proto': app.protocol_version, 'lang': app.language }, callback=ba.WeakCall(self._on_public_party_query_result)) _ba.run_transactions() def checkBox(val): global checkBoxBool checkBoxBool = bool(val) updatepartylist() if not searchText: widgetInstalled = True c_width = self._scroll_width c_height = self._scroll_height - 20 v = c_height - 30 searchText = txt = ba.textwidget( parent=self._tab_container, position=(c_width * 0.5 + 250, v + 105), color=ba.app.ui.title_color, scale=1.3, size=(150, 30), maxwidth=145, h_align='left', v_align='center', click_activate=True, selectable=True, autoselect=True, on_activate_call=lambda: self._set_internet_tab( 'host', playsound=True), editable=True, text='') ba.textwidget(parent=self._tab_container, position=(c_width * 0.5 + 125, v + 122), color=ba.app.ui.title_color, scale=1.1, size=(0, 0), h_align='left', v_align='center', on_activate_call=lambda: self._set_internet_tab( 'host', playsound=True), text='Search:') ba.checkboxwidget(parent=self._tab_container, text="Case-Sensitive", position=(c_width * 0.5 + 125, v + 135), color=ba.app.ui.title_color, textcolor=ba.app.ui.title_color, size=(50, 50), scale=1, value=False, on_value_change_call=checkBox) # pylint: disable=too-many-statements # Special case: if a party-queue window is up, don't do any of this # (keeps things smoother). if ba.app.ui.have_party_queue_window: return # If we've got a party-name text widget, keep its value plugged # into our public host name. text = self._internet_host_name_text if text: name = cast(str, ba.textwidget(query=self._internet_host_name_text)) _ba.set_public_party_name(name) # Show/hide the lock icon depending on if we've got pro. icon = self._internet_lock_icon if icon: if self._is_internet_locked(): ba.imagewidget(edit=icon, opacity=0.5) else: ba.imagewidget(edit=icon, opacity=0.0) if self._internet_tab == 'join': now = ba.time(ba.TimeType.REAL) if (now - self._internet_join_last_refresh_time > 0.001 * _ba.get_account_misc_read_val('pubPartyRefreshMS', 10000)): updatepartylist() search_text = cast(str, ba.textwidget(query=searchText)) if search_text != '': textModified = True x = [] if not checkBoxBool: search_text = search_text.lower() for i in self._public_parties: if not search_text in self._public_parties[i][ 'name'].lower(): x.append(i) else: for i in self._public_parties: if not search_text in self._public_parties[i]['name']: x.append(i) for i in x: del self._public_parties[i] self._first_public_party_list_rebuild_time = ba.time( ba.TimeType.REAL) + 1 self._rebuild_public_party_list() else: if textModified: updatepartylist() textModified = False # Go through our existing public party entries firing off pings # for any that have timed out. for party in list(self._public_parties.values()): if (party['next_ping_time'] <= now and ba.app.ping_thread_count < 15): # Make sure to fully catch up and not to multi-ping if # we're way behind somehow. while party['next_ping_time'] <= now: # Crank the interval up for high-latency parties to # save us some work. mult = 1 if party['ping'] is not None: mult = (10 if party['ping'] > 300 else 5 if party['ping'] > 150 else 2) party[ 'next_ping_time'] += party['ping_interval'] * mult class PingThread(threading.Thread): """Thread for sending out pings.""" def __init__(self, address: str, port: int, call: Callable[[str, int, Optional[int]], Optional[int]]): super().__init__() self._address = address self._port = port self._call = call def run(self) -> None: # pylint: disable=too-many-branches ba.app.ping_thread_count += 1 try: import socket from ba.internal import get_ip_address_type socket_type = get_ip_address_type( self._address) sock = socket.socket(socket_type, socket.SOCK_DGRAM) sock.connect((self._address, self._port)) accessible = False starttime = time.time() # Send a few pings and wait a second for # a response. sock.settimeout(1) for _i in range(3): sock.send(b'\x0b') result: Optional[bytes] try: # 11: BA_PACKET_SIMPLE_PING result = sock.recv(10) except Exception: result = None if result == b'\x0c': # 12: BA_PACKET_SIMPLE_PONG accessible = True break time.sleep(1) sock.close() ping = int((time.time() - starttime) * 1000.0) ba.pushcall(ba.Call( self._call, self._address, self._port, ping if accessible else None), from_other_thread=True) except ConnectionRefusedError: # Fine, server; sorry we pinged you. Hmph. pass except OSError as exc: import errno # Ignore harmless errors. if exc.errno in { errno.EHOSTUNREACH, errno.ENETUNREACH, }: pass elif exc.errno == 10022: # Windows 'invalid argument' error. pass elif exc.errno == 10051: # Windows 'a socket operation was attempted # to an unreachable network' error. pass elif exc.errno == errno.EADDRNOTAVAIL: if self._port == 0: # This has happened. Ignore. pass elif ba.do_once(): print( f'Got EADDRNOTAVAIL on gather ping' f' for addr {self._address}' f' port {self._port}.') else: ba.print_exception( f'Error on gather ping ' f'(errno={exc.errno})', once=True) except Exception: ba.print_exception('Error on gather ping', once=True) ba.app.ping_thread_count -= 1 PingThread(party['address'], party['port'], ba.WeakCall(self._ping_callback)).start()
def _rebuild(self) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals from bastd.ui.config import ConfigCheckBox from ba.modutils import show_user_scripts available_languages = ba.app.lang.available_languages # Don't rebuild if the menu is open or if our language and # language-list hasn't changed. # NOTE - although we now support widgets updating their own # translations, we still change the label formatting on the language # menu based on the language so still need this. ...however we could # make this more limited to it only rebuilds that one menu instead # of everything. if self._menu_open or (self._prev_lang == _ba.app.config.get( 'Lang', None) and self._prev_lang_list == available_languages): return self._prev_lang = _ba.app.config.get('Lang', None) self._prev_lang_list = available_languages # Clear out our sub-container. children = self._subcontainer.get_children() for child in children: child.delete() v = self._sub_height - 35 v -= self._spacing * 1.2 # Update our existing back button and title. if self._back_button is not None: ba.buttonwidget(edit=self._back_button, label=ba.Lstr(resource='backText')) ba.buttonwidget(edit=self._back_button, label=ba.charstr(ba.SpecialChar.BACK)) ba.textwidget(edit=self._title_text, text=ba.Lstr(resource=self._r + '.titleText')) this_button_width = 410 self._promo_code_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 14), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.enterPromoCodeText'), text_scale=1.0, on_activate_call=self._on_promo_code_press) if self._back_button is not None: ba.widget(edit=self._promo_code_button, up_widget=self._back_button, left_widget=self._back_button) v -= self._extra_button_spacing * 0.8 ba.textwidget(parent=self._subcontainer, position=(200, v + 10), size=(0, 0), text=ba.Lstr(resource=self._r + '.languageText'), maxwidth=150, scale=0.95, color=ba.app.ui.title_color, h_align='right', v_align='center') languages = _ba.app.lang.available_languages cur_lang = _ba.app.config.get('Lang', None) if cur_lang is None: cur_lang = 'Auto' # We have a special dict of language names in that language # so we don't have to go digging through each full language. try: import json with open('ba_data/data/langdata.json', encoding='utf-8') as infile: lang_names_translated = (json.loads( infile.read())['lang_names_translated']) except Exception: ba.print_exception('Error reading lang data.') lang_names_translated = {} langs_translated = {} for lang in languages: langs_translated[lang] = lang_names_translated.get(lang, lang) langs_full = {} for lang in languages: lang_translated = ba.Lstr(translate=('languages', lang)).evaluate() if langs_translated[lang] == lang_translated: langs_full[lang] = lang_translated else: langs_full[lang] = (langs_translated[lang] + ' (' + lang_translated + ')') self._language_popup = popup_ui.PopupMenu( parent=self._subcontainer, position=(210, v - 19), width=150, opening_call=ba.WeakCall(self._on_menu_open), closing_call=ba.WeakCall(self._on_menu_close), autoselect=False, on_value_change_call=ba.WeakCall(self._on_menu_choice), choices=['Auto'] + languages, button_size=(250, 60), choices_display=([ ba.Lstr(value=(ba.Lstr(resource='autoText').evaluate() + ' (' + ba.Lstr(translate=('languages', ba.app.lang.default_language )).evaluate() + ')')) ] + [ba.Lstr(value=langs_full[l]) for l in languages]), current_choice=cur_lang) v -= self._spacing * 1.8 ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v + 10), size=(0, 0), text=ba.Lstr(resource=self._r + '.helpTranslateText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))]), maxwidth=self._sub_width * 0.9, max_height=55, flatness=1.0, scale=0.65, color=(0.4, 0.9, 0.4, 0.8), h_align='center', v_align='center') v -= self._spacing * 1.9 this_button_width = 410 self._translation_editor_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 24), size=(this_button_width, 60), label=ba.Lstr(resource=self._r + '.translationEditorButtonText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) ]), autoselect=True, on_activate_call=ba.Call(ba.open_url, 'http://bombsquadgame.com/translate')) self._lang_status_text = ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v - 40), size=(0, 0), text='', flatness=1.0, scale=0.63, h_align='center', v_align='center', maxwidth=400.0) self._update_lang_status() v -= 40 lang_inform = _ba.get_account_misc_val('langInform', False) self._language_inform_checkbox = cbw = ba.checkboxwidget( parent=self._subcontainer, position=(50, v - 50), size=(self._sub_width - 100, 30), autoselect=True, maxwidth=430, textcolor=(0.8, 0.8, 0.8), value=lang_inform, text=ba.Lstr(resource=self._r + '.translationInformMe'), on_value_change_call=ba.WeakCall( self._on_lang_inform_value_change)) ba.widget(edit=self._translation_editor_button, down_widget=cbw, up_widget=self._language_popup.get_button()) v -= self._spacing * 3.0 self._kick_idle_players_check_box = ConfigCheckBox( parent=self._subcontainer, position=(50, v), size=(self._sub_width - 100, 30), configkey='Kick Idle Players', displayname=ba.Lstr(resource=self._r + '.kickIdlePlayersText'), scale=1.0, maxwidth=430) v -= 42 self._disable_camera_shake_check_box = ConfigCheckBox( parent=self._subcontainer, position=(50, v), size=(self._sub_width - 100, 30), configkey='Disable Camera Shake', displayname=ba.Lstr(resource=self._r + '.disableCameraShakeText'), scale=1.0, maxwidth=430) self._disable_gyro_check_box: Optional[ConfigCheckBox] = None if self._show_disable_gyro: v -= 42 self._disable_gyro_check_box = ConfigCheckBox( parent=self._subcontainer, position=(50, v), size=(self._sub_width - 100, 30), configkey='Disable Camera Gyro', displayname=ba.Lstr(resource=self._r + '.disableCameraGyroscopeMotionText'), scale=1.0, maxwidth=430) self._always_use_internal_keyboard_check_box: Optional[ConfigCheckBox] if self._show_always_use_internal_keyboard: v -= 42 self._always_use_internal_keyboard_check_box = ConfigCheckBox( parent=self._subcontainer, position=(50, v), size=(self._sub_width - 100, 30), configkey='Always Use Internal Keyboard', autoselect=True, displayname=ba.Lstr(resource=self._r + '.alwaysUseInternalKeyboardText'), scale=1.0, maxwidth=430) ba.textwidget( parent=self._subcontainer, position=(90, v - 10), size=(0, 0), text=ba.Lstr(resource=self._r + '.alwaysUseInternalKeyboardDescriptionText'), maxwidth=400, flatness=1.0, scale=0.65, color=(0.4, 0.9, 0.4, 0.8), h_align='left', v_align='center') v -= 20 else: self._always_use_internal_keyboard_check_box = None v -= self._spacing * 2.1 this_button_width = 410 self._show_user_mods_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 10), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.showUserModsText'), text_scale=1.0, on_activate_call=show_user_scripts) if self._show_always_use_internal_keyboard: assert self._always_use_internal_keyboard_check_box is not None ba.widget(edit=self._always_use_internal_keyboard_check_box.widget, down_widget=self._show_user_mods_button) ba.widget( edit=self._show_user_mods_button, up_widget=self._always_use_internal_keyboard_check_box.widget) else: ba.widget(edit=self._show_user_mods_button, up_widget=self._kick_idle_players_check_box.widget) ba.widget(edit=self._kick_idle_players_check_box.widget, down_widget=self._show_user_mods_button) v -= self._spacing * 2.0 self._modding_guide_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 10), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.moddingGuideText'), text_scale=1.0, on_activate_call=ba.Call( ba.open_url, 'http://www.froemling.net/docs/bombsquad-modding-guide')) v -= self._spacing * 2.0 self._plugins_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 10), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource='pluginsText'), text_scale=1.0, on_activate_call=self._on_plugins_button_press) v -= self._spacing * 0.6 self._vr_test_button: Optional[ba.Widget] if self._do_vr_test_button: v -= self._extra_button_spacing self._vr_test_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 14), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.vrTestingText'), text_scale=1.0, on_activate_call=self._on_vr_test_press) else: self._vr_test_button = None self._net_test_button: Optional[ba.Widget] if self._do_net_test_button: v -= self._extra_button_spacing self._net_test_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 14), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.netTestingText'), text_scale=1.0, on_activate_call=self._on_net_test_press) else: self._net_test_button = None v -= 70 self._benchmarks_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 14), size=(this_button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.benchmarksText'), text_scale=1.0, on_activate_call=self._on_benchmark_press) for child in self._subcontainer.get_children(): ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20) if ba.app.ui.use_toolbars: pbtn = _ba.get_special_widget('party_button') ba.widget(edit=self._scrollwidget, right_widget=pbtn) if self._back_button is None: ba.widget(edit=self._scrollwidget, left_widget=_ba.get_special_widget('back_button')) self._restore_state()
def __init__(self, transition: str = 'in_right', in_main_menu: bool = True, selected_profile: str = None, origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # pylint: disable=too-many-locals from ba.internal import ensure_have_account_player_profile self._in_main_menu = in_main_menu if self._in_main_menu: back_label = ba.Lstr(resource='backText') else: back_label = ba.Lstr(resource='doneText') uiscale = ba.app.uiscale self._width = 700.0 if uiscale is ba.UIScale.SMALL else 600.0 x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0 self._height = (360.0 if uiscale is ba.UIScale.SMALL else 385.0 if uiscale is ba.UIScale.MEDIUM else 410.0) # If we're being called up standalone, handle pause/resume ourself. if not self._in_main_menu: ba.app.pause() # If they provided an origin-widget, scale up from that. scale_origin: Optional[Tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None self._r = 'playerProfilesWindow' # Ensure we've got an account-profile in cases where we're signed in. ensure_have_account_player_profile() top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height + top_extra), transition=transition, scale_origin_stack_offset=scale_origin, scale=(2.2 if uiscale is ba.UIScale.SMALL else 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -14) if uiscale is ba.UIScale.SMALL else (0, 0))) self._back_button = btn = ba.buttonwidget( parent=self._root_widget, position=(40 + x_inset, self._height - 59), size=(120, 60), scale=0.8, label=back_label, button_type='back' if self._in_main_menu else None, autoselect=True, on_activate_call=self._back) ba.containerwidget(edit=self._root_widget, cancel_button=btn) ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height - 36), size=(0, 0), text=ba.Lstr(resource=self._r + '.titleText'), maxwidth=300, color=ba.app.title_color, scale=0.9, h_align='center', v_align='center') if self._in_main_menu: ba.buttonwidget(edit=btn, button_type='backSmall', size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) scroll_height = self._height - 140.0 self._scroll_width = self._width - (188 + x_inset * 2) v = self._height - 84.0 h = 50 + x_inset b_color = (0.6, 0.53, 0.63) scl = (1.055 if uiscale is ba.UIScale.SMALL else 1.18 if uiscale is ba.UIScale.MEDIUM else 1.3) v -= 70.0 * scl self._new_button = ba.buttonwidget(parent=self._root_widget, position=(h, v), size=(80, 66.0 * scl), on_activate_call=self._new_profile, color=b_color, button_type='square', autoselect=True, textcolor=(0.75, 0.7, 0.8), text_scale=0.7, label=ba.Lstr(resource=self._r + '.newButtonText')) v -= 70.0 * scl self._edit_button = ba.buttonwidget( parent=self._root_widget, position=(h, v), size=(80, 66.0 * scl), on_activate_call=self._edit_profile, color=b_color, button_type='square', autoselect=True, textcolor=(0.75, 0.7, 0.8), text_scale=0.7, label=ba.Lstr(resource=self._r + '.editButtonText')) v -= 70.0 * scl self._delete_button = ba.buttonwidget( parent=self._root_widget, position=(h, v), size=(80, 66.0 * scl), on_activate_call=self._delete_profile, color=b_color, button_type='square', autoselect=True, textcolor=(0.75, 0.7, 0.8), text_scale=0.7, label=ba.Lstr(resource=self._r + '.deleteButtonText')) v = self._height - 87 ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height - 71), size=(0, 0), text=ba.Lstr(resource=self._r + '.explanationText'), color=ba.app.infotextcolor, maxwidth=self._width * 0.83, scale=0.6, h_align='center', v_align='center') self._scrollwidget = ba.scrollwidget(parent=self._root_widget, highlight=False, position=(140 + x_inset, v - scroll_height), size=(self._scroll_width, scroll_height)) ba.widget(edit=self._scrollwidget, autoselect=True, left_widget=self._new_button) ba.containerwidget(edit=self._root_widget, selected_child=self._scrollwidget) self._columnwidget = ba.columnwidget(parent=self._scrollwidget) v -= 255 self._profiles: Optional[Dict[str, Dict[str, Any]]] = None self._selected_profile = selected_profile self._profile_widgets: List[ba.Widget] = [] self._refresh() self._restore_state()
def __init__(self, transition: str = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # If they provided an origin-widget, scale up from that. scale_origin: Optional[Tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None self._r = 'editSoundtrackWindow' self._width = 800 if ba.app.small_ui else 600 x_inset = 100 if ba.app.small_ui else 0 self._height = (340 if ba.app.small_ui else 370 if ba.app.med_ui else 440) spacing = 40.0 v = self._height - 40.0 v -= spacing * 1.0 super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height), transition=transition, toolbar_visibility='menu_minimal', scale_origin_stack_offset=scale_origin, scale=(2.3 if ba.app.small_ui else 1.6 if ba.app.med_ui else 1.0), stack_offset=(0, -18) if ba.app.small_ui else (0, 0))) if ba.app.toolbars and ba.app.small_ui: self._back_button = None else: self._back_button = ba.buttonwidget( parent=self._root_widget, position=(45 + x_inset, self._height - 60), size=(120, 60), scale=0.8, label=ba.Lstr(resource='backText'), button_type='back', autoselect=True) ba.buttonwidget(edit=self._back_button, button_type='backSmall', size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height - 35), size=(0, 0), maxwidth=300, text=ba.Lstr(resource=self._r + '.titleText'), color=ba.app.title_color, h_align="center", v_align="center") h = 43 + x_inset v = self._height - 60 b_color = (0.6, 0.53, 0.63) b_textcolor = (0.75, 0.7, 0.8) lock_tex = ba.gettexture('lock') self._lock_images: List[ba.Widget] = [] scl = (1.0 if ba.app.small_ui else 1.13 if ba.app.med_ui else 1.4) v -= 60.0 * scl self._new_button = btn = ba.buttonwidget( parent=self._root_widget, position=(h, v), size=(100, 55.0 * scl), on_activate_call=self._new_soundtrack, color=b_color, button_type='square', autoselect=True, textcolor=b_textcolor, text_scale=0.7, label=ba.Lstr(resource=self._r + '.newText')) self._lock_images.append( ba.imagewidget(parent=self._root_widget, size=(30, 30), draw_controller=btn, position=(h - 10, v + 55.0 * scl - 28), texture=lock_tex)) if self._back_button is None: ba.widget(edit=btn, left_widget=_ba.get_special_widget('back_button')) v -= 60.0 * scl self._edit_button = btn = ba.buttonwidget( parent=self._root_widget, position=(h, v), size=(100, 55.0 * scl), on_activate_call=self._edit_soundtrack, color=b_color, button_type='square', autoselect=True, textcolor=b_textcolor, text_scale=0.7, label=ba.Lstr(resource=self._r + '.editText')) self._lock_images.append( ba.imagewidget(parent=self._root_widget, size=(30, 30), draw_controller=btn, position=(h - 10, v + 55.0 * scl - 28), texture=lock_tex)) if self._back_button is None: ba.widget(edit=btn, left_widget=_ba.get_special_widget('back_button')) v -= 60.0 * scl self._duplicate_button = btn = ba.buttonwidget( parent=self._root_widget, position=(h, v), size=(100, 55.0 * scl), on_activate_call=self._duplicate_soundtrack, button_type='square', autoselect=True, color=b_color, textcolor=b_textcolor, text_scale=0.7, label=ba.Lstr(resource=self._r + '.duplicateText')) self._lock_images.append( ba.imagewidget(parent=self._root_widget, size=(30, 30), draw_controller=btn, position=(h - 10, v + 55.0 * scl - 28), texture=lock_tex)) if self._back_button is None: ba.widget(edit=btn, left_widget=_ba.get_special_widget('back_button')) v -= 60.0 * scl self._delete_button = btn = ba.buttonwidget( parent=self._root_widget, position=(h, v), size=(100, 55.0 * scl), on_activate_call=self._delete_soundtrack, color=b_color, button_type='square', autoselect=True, textcolor=b_textcolor, text_scale=0.7, label=ba.Lstr(resource=self._r + '.deleteText')) self._lock_images.append( ba.imagewidget(parent=self._root_widget, size=(30, 30), draw_controller=btn, position=(h - 10, v + 55.0 * scl - 28), texture=lock_tex)) if self._back_button is None: ba.widget(edit=btn, left_widget=_ba.get_special_widget('back_button')) # Keep our lock images up to date/etc. self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True) self._update() v = self._height - 65 scroll_height = self._height - 105 v -= scroll_height self._scrollwidget = scrollwidget = ba.scrollwidget( parent=self._root_widget, position=(152 + x_inset, v), highlight=False, size=(self._width - (205 + 2 * x_inset), scroll_height)) ba.widget(edit=self._scrollwidget, left_widget=self._new_button, right_widget=_ba.get_special_widget('party_button') if ba.app.toolbars else self._scrollwidget) self._col = ba.columnwidget(parent=scrollwidget) self._soundtracks: Optional[Dict[str, Any]] = None self._selected_soundtrack: Optional[str] = None self._selected_soundtrack_index: Optional[int] = None self._soundtrack_widgets: List[ba.Widget] = [] self._allow_changing_soundtracks = False self._refresh() if self._back_button is not None: ba.buttonwidget(edit=self._back_button, on_activate_call=self._back) ba.containerwidget(edit=self._root_widget, cancel_button=self._back_button) else: ba.containerwidget(edit=self._root_widget, on_cancel_call=self._back)
def instantiate_store_item_display(item_name: str, item: Dict[str, Any], parent_widget: ba.Widget, b_pos: Tuple[float, float], b_width: float, b_height: float, boffs_h: float = 0.0, boffs_h2: float = 0.0, boffs_v2: float = 0, delay: float = 0.0, button: bool = True) -> None: """(internal)""" # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals from ba.internal import (get_store_item, get_store_item_name_translated, get_clean_price) del boffs_h # unused arg del boffs_h2 # unused arg del boffs_v2 # unused arg item_info = get_store_item(item_name) title_v = 0.24 price_v = 0.145 base_text_scale = 1.0 item['name'] = title = get_store_item_name_translated(item_name) btn: Optional[ba.Widget] if button: item['button'] = btn = ba.buttonwidget(parent=parent_widget, position=b_pos, transition_delay=delay, show_buffer_top=76.0, enable_sound=False, button_type='square', size=(b_width, b_height), autoselect=True, label='') ba.widget(edit=btn, show_buffer_bottom=76.0) else: btn = None b_offs_x = -0.015 * b_width check_pos = 0.76 icon_tex = None tint_tex = None tint_color = None tint2_color = None tex_name: Optional[str] = None desc: Optional[str] = None modes: Optional[ba.Lstr] = None if item_name.startswith('characters.'): character = ba.app.spaz_appearances[item_info['character']] tint_color = ( item_info['color'] if 'color' in item_info else character.default_color if character.default_color is not None else (1, 1, 1)) tint2_color = (item_info['highlight'] if 'highlight' in item_info else character.default_highlight if character.default_highlight is not None else (1, 1, 1)) icon_tex = character.icon_texture tint_tex = character.icon_mask_texture title_v = 0.255 price_v = 0.145 elif item_name in ['upgrades.pro', 'pro']: base_text_scale = 0.6 title_v = 0.85 price_v = 0.15 elif item_name.startswith('maps.'): map_type = item_info['map_type'] tex_name = map_type.get_preview_texture_name() title_v = 0.312 price_v = 0.17 elif item_name.startswith('games.'): gametype = item_info['gametype'] modes_l = [] if gametype.supports_session_type(ba.CoopSession): modes_l.append(ba.Lstr(resource='playModes.coopText')) if gametype.supports_session_type(ba.DualTeamSession): modes_l.append(ba.Lstr(resource='playModes.teamsText')) if gametype.supports_session_type(ba.FreeForAllSession): modes_l.append(ba.Lstr(resource='playModes.freeForAllText')) if len(modes_l) == 3: modes = ba.Lstr(value='${A}, ${B}, ${C}', subs=[('${A}', modes_l[0]), ('${B}', modes_l[1]), ('${C}', modes_l[2])]) elif len(modes_l) == 2: modes = ba.Lstr(value='${A}, ${B}', subs=[('${A}', modes_l[0]), ('${B}', modes_l[1])]) elif len(modes_l) == 1: modes = modes_l[0] else: raise Exception() desc = gametype.get_description_display_string(ba.CoopSession) tex_name = item_info['previewTex'] base_text_scale = 0.8 title_v = 0.48 price_v = 0.17 elif item_name.startswith('icons.'): base_text_scale = 1.5 price_v = 0.2 check_pos = 0.6 if item_name.startswith('characters.'): frame_size = b_width * 0.7 im_dim = frame_size * (100.0 / 113.0) im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, b_pos[1] + b_height * 0.57 - im_dim * 0.5) mask_texture = ba.gettexture('characterIconMask') assert icon_tex is not None assert tint_tex is not None ba.imagewidget(parent=parent_widget, position=im_pos, size=(im_dim, im_dim), color=(1, 1, 1), transition_delay=delay, mask_texture=mask_texture, draw_controller=btn, texture=ba.gettexture(icon_tex), tint_texture=ba.gettexture(tint_tex), tint_color=tint_color, tint2_color=tint2_color) if item_name in ['pro', 'upgrades.pro']: frame_size = b_width * 0.5 im_dim = frame_size * (100.0 / 113.0) im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, b_pos[1] + b_height * 0.5 - im_dim * 0.5) ba.imagewidget(parent=parent_widget, position=im_pos, size=(im_dim, im_dim), transition_delay=delay, draw_controller=btn, color=(0.3, 0.0, 0.3), opacity=0.3, texture=ba.gettexture('logo')) txt = ba.Lstr(resource='store.bombSquadProNewDescriptionText') # t = 'foo\nfoo\nfoo\nfoo\nfoo\nfoo' item['descriptionText'] = ba.textwidget( parent=parent_widget, text=txt, position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.69), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale * 0.75, maxwidth=b_width * 0.75, max_height=b_height * 0.2, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, color=(0.3, 1, 0.3)) extra_backings = item['extra_backings'] = [] extra_images = item['extra_images'] = [] extra_texts = item['extra_texts'] = [] extra_texts_2 = item['extra_texts_2'] = [] backing_color = (0.5, 0.8, 0.3) if button else (0.6, 0.5, 0.65) b_square_texture = ba.gettexture('buttonSquare') char_mask_texture = ba.gettexture('characterIconMask') pos = (0.17, 0.43) tile_size = (b_width * 0.16 * 1.2, b_width * 0.2 * 1.2) tile_pos = (b_pos[0] + b_width * pos[0], b_pos[1] + b_height * pos[1]) extra_backings.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - tile_size[0] * 0.5, tile_pos[1] - tile_size[1] * 0.5), size=tile_size, transition_delay=delay, draw_controller=btn, color=backing_color, texture=b_square_texture)) im_size = tile_size[0] * 0.8 extra_images.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - im_size * 0.5, tile_pos[1] - im_size * 0.4), size=(im_size, im_size), transition_delay=delay, draw_controller=btn, color=(1, 1, 1), texture=ba.gettexture('ticketsMore'))) bonus_tickets = str( _ba.get_account_misc_read_val('proBonusTickets', 100)) extra_texts.append( ba.textwidget(parent=parent_widget, draw_controller=btn, position=(tile_pos[0] - tile_size[0] * 0.03, tile_pos[1] - tile_size[1] * 0.25), size=(0, 0), color=(0.6, 1, 0.6), transition_delay=delay, h_align='center', v_align='center', maxwidth=tile_size[0] * 0.7, scale=0.55, text=ba.Lstr(resource='getTicketsWindow.ticketsText', subs=[('${COUNT}', bonus_tickets)]), flatness=1.0, shadow=0.0)) for charname, pos in [('Kronk', (0.32, 0.45)), ('Zoe', (0.425, 0.4)), ('Jack Morgan', (0.555, 0.45)), ('Mel', (0.645, 0.4))]: tile_size = (b_width * 0.16 * 0.9, b_width * 0.2 * 0.9) tile_pos = (b_pos[0] + b_width * pos[0], b_pos[1] + b_height * pos[1]) character = ba.app.spaz_appearances[charname] extra_backings.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - tile_size[0] * 0.5, tile_pos[1] - tile_size[1] * 0.5), size=tile_size, transition_delay=delay, draw_controller=btn, color=backing_color, texture=b_square_texture)) im_size = tile_size[0] * 0.7 extra_images.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - im_size * 0.53, tile_pos[1] - im_size * 0.35), size=(im_size, im_size), transition_delay=delay, draw_controller=btn, color=(1, 1, 1), texture=ba.gettexture(character.icon_texture), tint_texture=ba.gettexture( character.icon_mask_texture), tint_color=character.default_color, tint2_color=character.default_highlight, mask_texture=char_mask_texture)) extra_texts.append( ba.textwidget(parent=parent_widget, draw_controller=btn, position=(tile_pos[0] - im_size * 0.03, tile_pos[1] - im_size * 0.51), size=(0, 0), color=(0.6, 1, 0.6), transition_delay=delay, h_align='center', v_align='center', maxwidth=tile_size[0] * 0.7, scale=0.55, text=ba.Lstr(translate=('characterNames', charname)), flatness=1.0, shadow=0.0)) # If we have a 'total-worth' item-id for this id, show that price so # the user knows how much this is worth. total_worth_item = _ba.get_account_misc_read_val('twrths', {}).get(item_name) total_worth_price: Optional[str] if total_worth_item is not None: price = _ba.get_price(total_worth_item) total_worth_price = (get_clean_price(price) if price is not None else '??') else: total_worth_price = None if total_worth_price is not None: total_worth_text = ba.Lstr(resource='store.totalWorthText', subs=[('${TOTAL_WORTH}', total_worth_price)]) extra_texts_2.append( ba.textwidget(parent=parent_widget, text=total_worth_text, position=(b_pos[0] + b_width * 0.5 + b_offs_x, b_pos[1] + b_height * 0.25), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale * 0.45, maxwidth=b_width * 0.5, size=(0, 0), h_align='center', v_align='center', shadow=1.0, flatness=1.0, draw_controller=btn, color=(0.3, 1, 1))) model_opaque = ba.getmodel('level_select_button_opaque') model_transparent = ba.getmodel('level_select_button_transparent') mask_tex = ba.gettexture('mapPreviewMask') for levelname, preview_tex_name, pos in [ ('Infinite Onslaught', 'doomShroomPreview', (0.80, 0.48)), ('Infinite Runaround', 'towerDPreview', (0.80, 0.32)) ]: tile_size = (b_width * 0.2, b_width * 0.13) tile_pos = (b_pos[0] + b_width * pos[0], b_pos[1] + b_height * pos[1]) im_size = tile_size[0] * 0.8 extra_backings.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - tile_size[0] * 0.5, tile_pos[1] - tile_size[1] * 0.5), size=tile_size, transition_delay=delay, draw_controller=btn, color=backing_color, texture=b_square_texture)) # Hack - gotta draw two transparent versions to avoid z issues. for mod in model_opaque, model_transparent: extra_images.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - im_size * 0.52, tile_pos[1] - im_size * 0.2), size=(im_size, im_size * 0.5), transition_delay=delay, model_transparent=mod, mask_texture=mask_tex, draw_controller=btn, texture=ba.gettexture(preview_tex_name))) extra_texts.append( ba.textwidget(parent=parent_widget, draw_controller=btn, position=(tile_pos[0] - im_size * 0.03, tile_pos[1] - im_size * 0.2), size=(0, 0), color=(0.6, 1, 0.6), transition_delay=delay, h_align='center', v_align='center', maxwidth=tile_size[0] * 0.7, scale=0.55, text=ba.Lstr(translate=('coopLevelNames', levelname)), flatness=1.0, shadow=0.0)) if item_name.startswith('icons.'): item['icon_text'] = ba.textwidget( parent=parent_widget, text=item_info['icon'], position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.5), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale * 2.0, maxwidth=b_width * 0.9, max_height=b_height * 0.9, size=(0, 0), h_align='center', v_align='center', draw_controller=btn) if item_name.startswith('maps.'): frame_size = b_width * 0.9 im_dim = frame_size * (100.0 / 113.0) im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, b_pos[1] + b_height * 0.62 - im_dim * 0.25) model_opaque = ba.getmodel('level_select_button_opaque') model_transparent = ba.getmodel('level_select_button_transparent') mask_tex = ba.gettexture('mapPreviewMask') assert tex_name is not None ba.imagewidget(parent=parent_widget, position=im_pos, size=(im_dim, im_dim * 0.5), transition_delay=delay, model_opaque=model_opaque, model_transparent=model_transparent, mask_texture=mask_tex, draw_controller=btn, texture=ba.gettexture(tex_name)) if item_name.startswith('games.'): frame_size = b_width * 0.8 im_dim = frame_size * (100.0 / 113.0) im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, b_pos[1] + b_height * 0.72 - im_dim * 0.25) model_opaque = ba.getmodel('level_select_button_opaque') model_transparent = ba.getmodel('level_select_button_transparent') mask_tex = ba.gettexture('mapPreviewMask') assert tex_name is not None ba.imagewidget(parent=parent_widget, position=im_pos, size=(im_dim, im_dim * 0.5), transition_delay=delay, model_opaque=model_opaque, model_transparent=model_transparent, mask_texture=mask_tex, draw_controller=btn, texture=ba.gettexture(tex_name)) item['descriptionText'] = ba.textwidget( parent=parent_widget, text=desc, position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.36), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale * 0.78, maxwidth=b_width * 0.8, max_height=b_height * 0.14, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, flatness=1.0, shadow=0.0, color=(0.6, 1, 0.6)) item['gameModesText'] = ba.textwidget( parent=parent_widget, text=modes, position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.26), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale * 0.65, maxwidth=b_width * 0.8, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, shadow=0, flatness=1.0, color=(0.6, 0.8, 0.6)) if not item_name.startswith('icons.'): item['title_text'] = ba.textwidget( parent=parent_widget, text=title, position=(b_pos[0] + b_width * 0.5 + b_offs_x, b_pos[1] + b_height * title_v), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale, maxwidth=b_width * 0.8, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, color=(0.7, 0.9, 0.7, 1.0)) item['purchase_check'] = ba.imagewidget( parent=parent_widget, position=(b_pos[0] + b_width * check_pos, b_pos[1] + b_height * 0.05), transition_delay=delay, model_transparent=ba.getmodel('checkTransparent'), opacity=0.0, size=(60, 60), color=(0.6, 0.5, 0.8), draw_controller=btn, texture=ba.gettexture('uiAtlas')) item['price_widget'] = ba.textwidget( parent=parent_widget, text='', position=(b_pos[0] + b_width * 0.5 + b_offs_x, b_pos[1] + b_height * price_v), transition_delay=delay, scale=b_width * (1.0 / 300.0) * base_text_scale, maxwidth=b_width * 0.9, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, color=(0.2, 1, 0.2, 1.0)) item['price_widget_left'] = ba.textwidget( parent=parent_widget, text='', position=(b_pos[0] + b_width * 0.33 + b_offs_x, b_pos[1] + b_height * price_v), transition_delay=delay, scale=b_width * (1.0 / 300.0) * base_text_scale, maxwidth=b_width * 0.3, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, color=(0.2, 1, 0.2, 0.5)) item['price_widget_right'] = ba.textwidget( parent=parent_widget, text='', position=(b_pos[0] + b_width * 0.66 + b_offs_x, b_pos[1] + b_height * price_v), transition_delay=delay, scale=1.1 * b_width * (1.0 / 300.0) * base_text_scale, maxwidth=b_width * 0.3, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, color=(0.2, 1, 0.2, 1.0)) item['price_slash_widget'] = ba.imagewidget( parent=parent_widget, position=(b_pos[0] + b_width * 0.33 + b_offs_x - 36, b_pos[1] + b_height * price_v - 35), transition_delay=delay, texture=ba.gettexture('slash'), opacity=0.0, size=(70, 70), draw_controller=btn, color=(1, 0, 0)) badge_rad = 44 badge_center = (b_pos[0] + b_width * 0.1 + b_offs_x, b_pos[1] + b_height * 0.87) item['sale_bg_widget'] = ba.imagewidget( parent=parent_widget, position=(badge_center[0] - badge_rad, badge_center[1] - badge_rad), opacity=0.0, transition_delay=delay, texture=ba.gettexture('circleZigZag'), draw_controller=btn, size=(badge_rad * 2, badge_rad * 2), color=(0.5, 0, 1)) item['sale_title_widget'] = ba.textwidget(parent=parent_widget, position=(badge_center[0], badge_center[1] + 12), transition_delay=delay, scale=1.0, maxwidth=badge_rad * 1.6, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, shadow=0.0, flatness=1.0, color=(0, 1, 0)) item['sale_time_widget'] = ba.textwidget(parent=parent_widget, position=(badge_center[0], badge_center[1] - 12), transition_delay=delay, scale=0.7, maxwidth=badge_rad * 1.6, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, shadow=0.0, flatness=1.0, color=(0.0, 1, 0.0, 1))
def _refresh(self, file_names: List[str], error: Optional[str]) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals if not self._root_widget: return scrollwidget_selected = (self._scrollwidget is None or self._root_widget.get_selected_child() == self._scrollwidget) in_top_folder = (self._path == self._base_path) hide_top_folder = in_top_folder and self._show_base_path is False if hide_top_folder: folder_name = '' elif self._path == '/': folder_name = '/' else: assert self._path is not None folder_name = os.path.basename(self._path) b_color = (0.6, 0.53, 0.63) b_color_disabled = (0.65, 0.65, 0.65) if len(self._recent_paths) < 2: ba.buttonwidget(edit=self._back_button, color=b_color_disabled, textcolor=(0.5, 0.5, 0.5)) else: ba.buttonwidget(edit=self._back_button, color=b_color, textcolor=(0.75, 0.7, 0.8)) max_str_width = 300.0 str_width = min( max_str_width, _ba.get_string_width(folder_name, suppress_warning=True)) ba.textwidget(edit=self._path_text, text=folder_name, maxwidth=max_str_width) ba.imagewidget(edit=self._folder_icon, position=(self._folder_center - str_width * 0.5 - 40, self._height - 117), opacity=0.0 if hide_top_folder else 1.0) if self._scrollwidget is not None: self._scrollwidget.delete() if self._use_folder_button is not None: self._use_folder_button.delete() ba.widget(edit=self._cancel_button, right_widget=self._back_button) self._scrollwidget = ba.scrollwidget( parent=self._root_widget, position=((self._width - self._scroll_width) * 0.5, self._height - self._scroll_height - 119), size=(self._scroll_width, self._scroll_height)) if scrollwidget_selected: ba.containerwidget(edit=self._root_widget, selected_child=self._scrollwidget) # show error case.. if error is not None: self._subcontainer = ba.containerwidget(parent=self._scrollwidget, size=(self._scroll_width, self._scroll_height), background=False) ba.textwidget(parent=self._subcontainer, color=(1, 1, 0, 1), text=error, maxwidth=self._scroll_width * 0.9, position=(self._scroll_width * 0.48, self._scroll_height * 0.57), size=(0, 0), h_align='center', v_align='center') else: file_names = [f for f in file_names if not f.startswith('.')] file_names.sort(key=lambda x: x[0].lower()) entries = file_names entry_height = 35 folder_entry_height = 100 show_folder_entry = False show_use_folder_button = (self._allow_folders and not in_top_folder) self._subcontainerheight = entry_height * len(entries) + ( folder_entry_height if show_folder_entry else 0) v = self._subcontainerheight - (folder_entry_height if show_folder_entry else 0) self._subcontainer = ba.containerwidget( parent=self._scrollwidget, size=(self._scroll_width, self._subcontainerheight), background=False) ba.containerwidget(edit=self._scrollwidget, claims_left_right=False, claims_tab=False) ba.containerwidget(edit=self._subcontainer, claims_left_right=False, claims_tab=False, selection_loops=False, print_list_exit_instructions=False) ba.widget(edit=self._subcontainer, up_widget=self._back_button) if show_use_folder_button: self._use_folder_button = btn = ba.buttonwidget( parent=self._root_widget, position=(self._width - self._button_width - 35 - self._x_inset, self._height - 67), size=(self._button_width, 50), label=ba.Lstr(resource=self._r + '.useThisFolderButtonText'), on_activate_call=self._on_folder_entry_activated) ba.widget(edit=btn, left_widget=self._cancel_button, down_widget=self._scrollwidget) ba.widget(edit=self._cancel_button, right_widget=btn) ba.containerwidget(edit=self._root_widget, start_button=btn) folder_icon_size = 35 for num, entry in enumerate(entries): cnt = ba.containerwidget( parent=self._subcontainer, position=(0, v - entry_height), size=(self._scroll_width, entry_height), root_selectable=True, background=False, click_activate=True, on_activate_call=ba.Call(self._on_entry_activated, entry)) if num == 0: ba.widget(edit=cnt, up_widget=self._back_button) is_valid_file_path = self._is_valid_file_path(entry) assert self._path is not None is_dir = os.path.isdir(self._path + '/' + entry) if is_dir: ba.imagewidget(parent=cnt, size=(folder_icon_size, folder_icon_size), position=(10, 0.5 * entry_height - folder_icon_size * 0.5), draw_controller=cnt, texture=self._folder_tex, color=self._folder_color) else: ba.imagewidget(parent=cnt, size=(folder_icon_size, folder_icon_size), position=(10, 0.5 * entry_height - folder_icon_size * 0.5), opacity=1.0 if is_valid_file_path else 0.5, draw_controller=cnt, texture=self._file_tex, color=self._file_color) ba.textwidget(parent=cnt, draw_controller=cnt, text=entry, h_align='left', v_align='center', position=(10 + folder_icon_size * 1.05, entry_height * 0.5), size=(0, 0), maxwidth=self._scroll_width * 0.93 - 50, color=(1, 1, 1, 1) if (is_valid_file_path or is_dir) else (0.5, 0.5, 0.5, 1)) v -= entry_height
def __init__(self, origin_widget: ba.Widget = None): scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None transition = 'in_right' bg_color = (0.4, 0.4, 0.5) self._width = 560 self._height = 420 uiscale = ba.app.ui.uiscale base_scale = (1.65 if uiscale is ba.UIScale.SMALL else 1.5 if uiscale is ba.UIScale.MEDIUM else 1.1) super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height), transition=transition, scale=base_scale, scale_origin_stack_offset=scale_origin, stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0))) self._cancel_button = ba.buttonwidget(parent=self._root_widget, position=(40, self._height - 45), size=(50, 50), scale=0.7, label='', color=bg_color, on_activate_call=self._cancel, autoselect=True, icon=ba.gettexture('crossOut'), iconscale=1.2) maxlinks = _ba.get_v1_account_misc_read_val('maxLinkAccounts', 5) ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height * 0.56), size=(0, 0), text=ba.Lstr(resource=( 'accountSettingsWindow.linkAccountsInstructionsNewText'), subs=[('${COUNT}', str(maxlinks))]), maxwidth=self._width * 0.9, color=ba.app.ui.infotextcolor, max_height=self._height * 0.6, h_align='center', v_align='center') ba.containerwidget(edit=self._root_widget, cancel_button=self._cancel_button) ba.buttonwidget( parent=self._root_widget, position=(40, 30), size=(200, 60), label=ba.Lstr( resource='accountSettingsWindow.linkAccountsGenerateCodeText'), autoselect=True, on_activate_call=self._generate_press) self._enter_code_button = ba.buttonwidget( parent=self._root_widget, position=(self._width - 240, 30), size=(200, 60), label=ba.Lstr( resource='accountSettingsWindow.linkAccountsEnterCodeText'), autoselect=True, on_activate_call=self._enter_code_press)
def _refresh(self) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=cyclic-import from bastd.ui import confirm account_state = _ba.get_account_state() account_type = (_ba.get_account_type() if account_state == 'signed_in' else 'unknown') is_google = account_type == 'Google Play' show_local_signed_in_as = False local_signed_in_as_space = 50.0 show_signed_in_as = self._signed_in signed_in_as_space = 95.0 show_sign_in_benefits = not self._signed_in sign_in_benefits_space = 80.0 show_signing_in_text = account_state == 'signing_in' signing_in_text_space = 80.0 show_google_play_sign_in_button = (account_state == 'signed_out' and 'Google Play' in self._show_sign_in_buttons) show_game_circle_sign_in_button = (account_state == 'signed_out' and 'Game Circle' in self._show_sign_in_buttons) show_ali_sign_in_button = (account_state == 'signed_out' and 'Ali' in self._show_sign_in_buttons) show_test_sign_in_button = (account_state == 'signed_out' and 'Test' in self._show_sign_in_buttons) show_device_sign_in_button = (account_state == 'signed_out' and 'Local' in self._show_sign_in_buttons) sign_in_button_space = 70.0 show_game_service_button = (self._signed_in and account_type in ['Game Center', 'Game Circle']) game_service_button_space = 60.0 show_linked_accounts_text = (self._signed_in and _ba.get_account_misc_read_val( 'allowAccountLinking2', False)) linked_accounts_text_space = 60.0 show_achievements_button = (self._signed_in and account_type in ('Google Play', 'Alibaba', 'Local', 'OUYA', 'Test')) achievements_button_space = 60.0 show_achievements_text = (self._signed_in and not show_achievements_button) achievements_text_space = 27.0 show_leaderboards_button = (self._signed_in and is_google) leaderboards_button_space = 60.0 show_campaign_progress = self._signed_in campaign_progress_space = 27.0 show_tickets = self._signed_in tickets_space = 27.0 show_reset_progress_button = False reset_progress_button_space = 70.0 show_player_profiles_button = self._signed_in player_profiles_button_space = 100.0 show_link_accounts_button = (self._signed_in and _ba.get_account_misc_read_val( 'allowAccountLinking2', False)) link_accounts_button_space = 70.0 show_unlink_accounts_button = show_link_accounts_button unlink_accounts_button_space = 90.0 show_sign_out_button = (self._signed_in and account_type in ['Test', 'Local', 'Google Play']) sign_out_button_space = 70.0 if self._subcontainer is not None: self._subcontainer.delete() self._sub_height = 60.0 if show_local_signed_in_as: self._sub_height += local_signed_in_as_space if show_signed_in_as: self._sub_height += signed_in_as_space if show_signing_in_text: self._sub_height += signing_in_text_space if show_google_play_sign_in_button: self._sub_height += sign_in_button_space if show_game_circle_sign_in_button: self._sub_height += sign_in_button_space if show_ali_sign_in_button: self._sub_height += sign_in_button_space if show_test_sign_in_button: self._sub_height += sign_in_button_space if show_device_sign_in_button: self._sub_height += sign_in_button_space if show_game_service_button: self._sub_height += game_service_button_space if show_linked_accounts_text: self._sub_height += linked_accounts_text_space if show_achievements_text: self._sub_height += achievements_text_space if show_achievements_button: self._sub_height += achievements_button_space if show_leaderboards_button: self._sub_height += leaderboards_button_space if show_campaign_progress: self._sub_height += campaign_progress_space if show_tickets: self._sub_height += tickets_space if show_sign_in_benefits: self._sub_height += sign_in_benefits_space if show_reset_progress_button: self._sub_height += reset_progress_button_space if show_player_profiles_button: self._sub_height += player_profiles_button_space if show_link_accounts_button: self._sub_height += link_accounts_button_space if show_unlink_accounts_button: self._sub_height += unlink_accounts_button_space if show_sign_out_button: self._sub_height += sign_out_button_space self._subcontainer = ba.containerwidget(parent=self._scrollwidget, size=(self._sub_width, self._sub_height), background=False) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True, claims_tab=True, selection_loop_to_parent=True) ba.containerwidget(edit=self._subcontainer, claims_left_right=True, claims_tab=True, selection_loop_to_parent=True) first_selectable = None v = self._sub_height - 10.0 if show_local_signed_in_as: v -= local_signed_in_as_space * 0.6 ba.textwidget( parent=self._subcontainer, position=(self._sub_width * 0.5, v), size=(0, 0), text=ba.Lstr( resource='accountSettingsWindow.deviceSpecificAccountText', subs=[('${NAME}', _ba.get_account_display_string())]), scale=0.7, color=(0.5, 0.5, 0.6), maxwidth=self._sub_width * 0.9, flatness=1.0, h_align='center', v_align='center') v -= local_signed_in_as_space * 0.4 self._account_name_text: Optional[ba.Widget] if show_signed_in_as: v -= signed_in_as_space * 0.2 txt = ba.Lstr( resource='accountSettingsWindow.youAreSignedInAsText', fallback_resource='accountSettingsWindow.youAreLoggedInAsText') ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v), size=(0, 0), text=txt, scale=0.9, color=ba.app.ui.title_color, maxwidth=self._sub_width * 0.9, h_align='center', v_align='center') v -= signed_in_as_space * 0.4 self._account_name_text = ba.textwidget( parent=self._subcontainer, position=(self._sub_width * 0.5, v), size=(0, 0), scale=1.5, maxwidth=self._sub_width * 0.9, res_scale=1.5, color=(1, 1, 1, 1), h_align='center', v_align='center') self._refresh_account_name_text() v -= signed_in_as_space * 0.4 else: self._account_name_text = None if self._back_button is None: bbtn = _ba.get_special_widget('back_button') else: bbtn = self._back_button if show_sign_in_benefits: v -= sign_in_benefits_space app = ba.app extra: Optional[Union[str, ba.Lstr]] if (app.platform in ['mac', 'ios'] and app.subplatform == 'appstore'): extra = ba.Lstr( value='\n${S}', subs=[('${S}', ba.Lstr(resource='signInWithGameCenterText'))]) else: extra = '' ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v + sign_in_benefits_space * 0.4), size=(0, 0), text=ba.Lstr(value='${A}${B}', subs=[('${A}', ba.Lstr(resource=self._r + '.signInInfoText')), ('${B}', extra)]), max_height=sign_in_benefits_space * 0.9, scale=0.9, color=(0.75, 0.7, 0.8), maxwidth=self._sub_width * 0.8, h_align='center', v_align='center') if show_signing_in_text: v -= signing_in_text_space ba.textwidget( parent=self._subcontainer, position=(self._sub_width * 0.5, v + signing_in_text_space * 0.5), size=(0, 0), text=ba.Lstr(resource='accountSettingsWindow.signingInText'), scale=0.9, color=(0, 1, 0), maxwidth=self._sub_width * 0.8, h_align='center', v_align='center') if show_google_play_sign_in_button: button_width = 350 v -= sign_in_button_space self._sign_in_google_play_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v - 20), autoselect=True, size=(button_width, 60), label=ba.Lstr( value='${A}${B}', subs=[('${A}', ba.charstr(ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO)), ('${B}', ba.Lstr(resource=self._r + '.signInWithGooglePlayText'))]), on_activate_call=lambda: self._sign_in_press('Google Play')) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn) ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) self._sign_in_text = None if show_game_circle_sign_in_button: button_width = 350 v -= sign_in_button_space self._sign_in_game_circle_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v - 20), autoselect=True, size=(button_width, 60), label=ba.Lstr(value='${A}${B}', subs=[('${A}', ba.charstr( ba.SpecialChar.GAME_CIRCLE_LOGO)), ('${B}', ba.Lstr(resource=self._r + '.signInWithGameCircleText'))]), on_activate_call=lambda: self._sign_in_press('Game Circle')) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn) ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) self._sign_in_text = None if show_ali_sign_in_button: button_width = 350 v -= sign_in_button_space self._sign_in_ali_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v - 20), autoselect=True, size=(button_width, 60), label=ba.Lstr(value='${A}${B}', subs=[('${A}', ba.charstr(ba.SpecialChar.ALIBABA_LOGO)), ('${B}', ba.Lstr(resource=self._r + '.signInText')) ]), on_activate_call=lambda: self._sign_in_press('Ali')) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn) ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) self._sign_in_text = None if show_device_sign_in_button: button_width = 350 v -= sign_in_button_space self._sign_in_device_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v - 20), autoselect=True, size=(button_width, 60), label='', on_activate_call=lambda: self._sign_in_press('Local')) ba.textwidget(parent=self._subcontainer, draw_controller=btn, h_align='center', v_align='center', size=(0, 0), position=(self._sub_width * 0.5, v + 17), text=ba.Lstr( value='${A}${B}', subs=[('${A}', ba.charstr(ba.SpecialChar.LOCAL_ACCOUNT)), ('${B}', ba.Lstr(resource=self._r + '.signInWithDeviceText'))]), maxwidth=button_width * 0.8, color=(0.75, 1.0, 0.7)) ba.textwidget(parent=self._subcontainer, draw_controller=btn, h_align='center', v_align='center', size=(0, 0), position=(self._sub_width * 0.5, v - 4), text=ba.Lstr(resource=self._r + '.signInWithDeviceInfoText'), flatness=1.0, scale=0.57, maxwidth=button_width * 0.9, color=(0.55, 0.8, 0.5)) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn) ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) self._sign_in_text = None # Old test-account option. if show_test_sign_in_button: button_width = 350 v -= sign_in_button_space self._sign_in_test_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v - 20), autoselect=True, size=(button_width, 60), label='', on_activate_call=lambda: self._sign_in_press('Test')) ba.textwidget(parent=self._subcontainer, draw_controller=btn, h_align='center', v_align='center', size=(0, 0), position=(self._sub_width * 0.5, v + 17), text=ba.Lstr( value='${A}${B}', subs=[('${A}', ba.charstr(ba.SpecialChar.TEST_ACCOUNT)), ('${B}', ba.Lstr(resource=self._r + '.signInWithTestAccountText'))]), maxwidth=button_width * 0.8, color=(0.75, 1.0, 0.7)) ba.textwidget(parent=self._subcontainer, draw_controller=btn, h_align='center', v_align='center', size=(0, 0), position=(self._sub_width * 0.5, v - 4), text=ba.Lstr(resource=self._r + '.signInWithTestAccountInfoText'), flatness=1.0, scale=0.57, maxwidth=button_width * 0.9, color=(0.55, 0.8, 0.5)) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn) ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) self._sign_in_text = None if show_player_profiles_button: button_width = 300 v -= player_profiles_button_space self._player_profiles_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v + 30), autoselect=True, size=(button_width, 60), label=ba.Lstr(resource='playerProfilesWindow.titleText'), color=(0.55, 0.5, 0.6), icon=ba.gettexture('cuteSpaz'), textcolor=(0.75, 0.7, 0.8), on_activate_call=self._player_profiles_press) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=0) # the button to go to OS-Specific leaderboards/high-score-lists/etc. if show_game_service_button: button_width = 300 v -= game_service_button_space * 0.85 account_type = _ba.get_account_type() if account_type == 'Game Center': account_type_name = ba.Lstr(resource='gameCenterText') elif account_type == 'Game Circle': account_type_name = ba.Lstr(resource='gameCircleText') else: raise ValueError("unknown account type: '" + str(account_type) + "'") self._game_service_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v), color=(0.55, 0.5, 0.6), textcolor=(0.75, 0.7, 0.8), autoselect=True, on_activate_call=_ba.show_online_score_ui, size=(button_width, 50), label=account_type_name) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn) v -= game_service_button_space * 0.15 else: self.game_service_button = None self._achievements_text: Optional[ba.Widget] if show_achievements_text: v -= achievements_text_space * 0.5 self._achievements_text = ba.textwidget( parent=self._subcontainer, position=(self._sub_width * 0.5, v), size=(0, 0), scale=0.9, color=(0.75, 0.7, 0.8), maxwidth=self._sub_width * 0.8, h_align='center', v_align='center') v -= achievements_text_space * 0.5 else: self._achievements_text = None self._achievements_button: Optional[ba.Widget] if show_achievements_button: button_width = 300 v -= achievements_button_space * 0.85 self._achievements_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v), color=(0.55, 0.5, 0.6), textcolor=(0.75, 0.7, 0.8), autoselect=True, icon=ba.gettexture('googlePlayAchievementsIcon' if is_google else 'achievementsIcon'), icon_color=(0.8, 0.95, 0.7) if is_google else (0.85, 0.8, 0.9), on_activate_call=self._on_achievements_press, size=(button_width, 50), label='') if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn) v -= achievements_button_space * 0.15 else: self._achievements_button = None if show_achievements_text or show_achievements_button: self._refresh_achievements() self._leaderboards_button: Optional[ba.Widget] if show_leaderboards_button: button_width = 300 v -= leaderboards_button_space * 0.85 self._leaderboards_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v), color=(0.55, 0.5, 0.6), textcolor=(0.75, 0.7, 0.8), autoselect=True, icon=ba.gettexture('googlePlayLeaderboardsIcon'), icon_color=(0.8, 0.95, 0.7), on_activate_call=self._on_leaderboards_press, size=(button_width, 50), label=ba.Lstr(resource='leaderboardsText')) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn) v -= leaderboards_button_space * 0.15 else: self._leaderboards_button = None self._campaign_progress_text: Optional[ba.Widget] if show_campaign_progress: v -= campaign_progress_space * 0.5 self._campaign_progress_text = ba.textwidget( parent=self._subcontainer, position=(self._sub_width * 0.5, v), size=(0, 0), scale=0.9, color=(0.75, 0.7, 0.8), maxwidth=self._sub_width * 0.8, h_align='center', v_align='center') v -= campaign_progress_space * 0.5 self._refresh_campaign_progress_text() else: self._campaign_progress_text = None self._tickets_text: Optional[ba.Widget] if show_tickets: v -= tickets_space * 0.5 self._tickets_text = ba.textwidget(parent=self._subcontainer, position=(self._sub_width * 0.5, v), size=(0, 0), scale=0.9, color=(0.75, 0.7, 0.8), maxwidth=self._sub_width * 0.8, flatness=1.0, h_align='center', v_align='center') v -= tickets_space * 0.5 self._refresh_tickets_text() else: self._tickets_text = None # bit of spacing before the reset/sign-out section v -= 5 button_width = 250 if show_reset_progress_button: confirm_text = (ba.Lstr(resource=self._r + '.resetProgressConfirmText') if self._can_reset_achievements else ba.Lstr( resource=self._r + '.resetProgressConfirmNoAchievementsText')) v -= reset_progress_button_space self._reset_progress_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v), color=(0.55, 0.5, 0.6), textcolor=(0.75, 0.7, 0.8), autoselect=True, size=(button_width, 60), label=ba.Lstr(resource=self._r + '.resetProgressText'), on_activate_call=lambda: confirm.ConfirmWindow( text=confirm_text, width=500, height=200, action=self._reset_progress)) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn) self._linked_accounts_text: Optional[ba.Widget] if show_linked_accounts_text: v -= linked_accounts_text_space * 0.8 self._linked_accounts_text = ba.textwidget( parent=self._subcontainer, position=(self._sub_width * 0.5, v), size=(0, 0), scale=0.9, color=(0.75, 0.7, 0.8), maxwidth=self._sub_width * 0.95, h_align='center', v_align='center') v -= linked_accounts_text_space * 0.2 self._update_linked_accounts_text() else: self._linked_accounts_text = None if show_link_accounts_button: v -= link_accounts_button_space self._link_accounts_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v), autoselect=True, size=(button_width, 60), label='', color=(0.55, 0.5, 0.6), on_activate_call=self._link_accounts_press) ba.textwidget(parent=self._subcontainer, draw_controller=btn, h_align='center', v_align='center', size=(0, 0), position=(self._sub_width * 0.5, v + 17 + 20), text=ba.Lstr(resource=self._r + '.linkAccountsText'), maxwidth=button_width * 0.8, color=(0.75, 0.7, 0.8)) ba.textwidget(parent=self._subcontainer, draw_controller=btn, h_align='center', v_align='center', size=(0, 0), position=(self._sub_width * 0.5, v - 4 + 20), text=ba.Lstr(resource=self._r + '.linkAccountsInfoText'), flatness=1.0, scale=0.5, maxwidth=button_width * 0.8, color=(0.75, 0.7, 0.8)) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) self._unlink_accounts_button: Optional[ba.Widget] if show_unlink_accounts_button: v -= unlink_accounts_button_space self._unlink_accounts_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v + 25), autoselect=True, size=(button_width, 60), label='', color=(0.55, 0.5, 0.6), on_activate_call=self._unlink_accounts_press) self._unlink_accounts_button_label = ba.textwidget( parent=self._subcontainer, draw_controller=btn, h_align='center', v_align='center', size=(0, 0), position=(self._sub_width * 0.5, v + 55), text=ba.Lstr(resource=self._r + '.unlinkAccountsText'), maxwidth=button_width * 0.8, color=(0.75, 0.7, 0.8)) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) self._update_unlink_accounts_button() else: self._unlink_accounts_button = None if show_sign_out_button: v -= sign_out_button_space self._sign_out_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v), size=(button_width, 60), label=ba.Lstr(resource=self._r + '.signOutText'), color=(0.55, 0.5, 0.6), textcolor=(0.75, 0.7, 0.8), autoselect=True, on_activate_call=self._sign_out_press) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: ba.widget(edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15) # Whatever the topmost selectable thing is, we want it to scroll all # the way up when we select it. if first_selectable is not None: ba.widget(edit=first_selectable, up_widget=bbtn, show_buffer_top=400) # (this should re-scroll us to the top..) ba.containerwidget(edit=self._subcontainer, visible_child=first_selectable) self._needs_refresh = False
def _update(self) -> None: # pylint: disable=too-many-locals # pylint: disable=too-many-branches # pylint: disable=too-many-statements # pylint: disable=too-many-nested-blocks # update muted state if ba.app.config.resolve('Chat Muted'): ba.textwidget(edit=self._muted_text, color=(1, 1, 1, 0.3)) # clear any chat texts we're showing if self._chat_texts: while self._chat_texts: first = self._chat_texts.pop() first.delete() else: ba.textwidget(edit=self._muted_text, color=(1, 1, 1, 0.0)) # update roster section roster = _ba.get_game_roster() if roster != self._roster: self._roster = roster # clear out old for widget in self._name_widgets: widget.delete() self._name_widgets = [] if not self._roster: top_section_height = 60 ba.textwidget(edit=self._empty_str, text=ba.Lstr(resource=self._r + '.emptyText')) ba.scrollwidget(edit=self._scrollwidget, size=(self._width - 50, self._height - top_section_height - 110), position=(30, 80)) else: columns = 1 if len( self._roster) == 1 else 2 if len(self._roster) == 2 else 3 rows = int(math.ceil(float(len(self._roster)) / columns)) c_width = (self._width * 0.9) / max(3, columns) c_width_total = c_width * columns c_height = 24 c_height_total = c_height * rows for y in range(rows): for x in range(columns): index = y * columns + x if index < len(self._roster): t_scale = 0.65 pos = (self._width * 0.53 - c_width_total * 0.5 + c_width * x - 23, self._height - 65 - c_height * y - 15) # if there are players present for this client, use # their names as a display string instead of the # client spec-string try: if self._roster[index]['players']: # if there's just one, use the full name; # otherwise combine short names if len(self._roster[index] ['players']) == 1: p_str = self._roster[index]['players'][ 0]['name_full'] else: p_str = ('/'.join([ entry['name'] for entry in self._roster[index]['players'] ])) if len(p_str) > 25: p_str = p_str[:25] + '...' else: p_str = self._roster[index][ 'display_string'] except Exception: ba.print_exception( 'Error calcing client name str.') p_str = '???' widget = ba.textwidget(parent=self._root_widget, position=(pos[0], pos[1]), scale=t_scale, size=(c_width * 0.85, 30), maxwidth=c_width * 0.85, color=(1, 1, 1) if index == 0 else (1, 1, 1), selectable=True, autoselect=True, click_activate=True, text=ba.Lstr(value=p_str), h_align='left', v_align='center') self._name_widgets.append(widget) # in newer versions client_id will be present and # we can use that to determine who the host is. # in older versions we assume the first client is # host if self._roster[index]['client_id'] is not None: is_host = self._roster[index][ 'client_id'] == -1 else: is_host = (index == 0) # FIXME: Should pass client_id to these sort of # calls; not spec-string (perhaps should wait till # client_id is more readily available though). ba.textwidget(edit=widget, on_activate_call=ba.Call( self._on_party_member_press, self._roster[index]['client_id'], is_host, widget)) pos = (self._width * 0.53 - c_width_total * 0.5 + c_width * x, self._height - 65 - c_height * y) # Make the assumption that the first roster # entry is the server. # FIXME: Shouldn't do this. if is_host: twd = min( c_width * 0.85, _ba.get_string_width( p_str, suppress_warning=True) * t_scale) self._name_widgets.append( ba.textwidget( parent=self._root_widget, position=(pos[0] + twd + 1, pos[1] - 0.5), size=(0, 0), h_align='left', v_align='center', maxwidth=c_width * 0.96 - twd, color=(0.1, 1, 0.1, 0.5), text=ba.Lstr(resource=self._r + '.hostText'), scale=0.4, shadow=0.1, flatness=1.0)) ba.textwidget(edit=self._empty_str, text='') ba.scrollwidget(edit=self._scrollwidget, size=(self._width - 50, max(100, self._height - 139 - c_height_total)), position=(30, 80))
def __init__(self, offer: Dict[str, Any], transition: str = 'in_right'): # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals from ba.internal import (get_store_item_display_size, get_clean_price) from ba import SpecialChar from bastd.ui.store import item as storeitemui self._cancel_delay = offer.get('cancelDelay', 0) # First thing: if we're offering pro or an IAP, see if we have a # price for it. # If not, abort and go into zombie mode (the user should never see # us that way). real_price: Optional[str] # Misnomer: 'pro' actually means offer 'pro_sale'. if offer['item'] in ['pro', 'pro_fullprice']: real_price = _ba.get_price('pro' if offer['item'] == 'pro_fullprice' else 'pro_sale') if real_price is None and ba.app.debug_build: print('NOTE: Faking prices for debug build.') real_price = '$1.23' zombie = real_price is None elif isinstance(offer['price'], str): # (a string price implies IAP id) real_price = _ba.get_price(offer['price']) if real_price is None and ba.app.debug_build: print('NOTE: Faking price for debug build.') real_price = '$1.23' zombie = real_price is None else: real_price = None zombie = False if real_price is None: real_price = '?' if offer['item'] in ['pro', 'pro_fullprice']: self._offer_item = 'pro' else: self._offer_item = offer['item'] # If we wanted a real price but didn't find one, go zombie. if zombie: return # This can pop up suddenly, so lets block input for 1 second. _ba.lock_all_input() ba.timer(1.0, _ba.unlock_all_input, timetype=ba.TimeType.REAL) ba.playsound(ba.getsound('ding')) ba.timer(0.3, lambda: ba.playsound(ba.getsound('ooh')), timetype=ba.TimeType.REAL) self._offer = copy.deepcopy(offer) self._width = 580 self._height = 590 uiscale = ba.app.ui.uiscale super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height), transition=transition, scale=(1.2 if uiscale is ba.UIScale.SMALL else 1.15 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else (0, 0))) self._is_bundle_sale = False try: if offer['item'] in ['pro', 'pro_fullprice']: original_price_str = _ba.get_price('pro') if original_price_str is None: original_price_str = '?' new_price_str = _ba.get_price('pro_sale') if new_price_str is None: new_price_str = '?' percent_off_text = '' else: # If the offer includes bonus tickets, it's a bundle-sale. if ('bonusTickets' in offer and offer['bonusTickets'] is not None): self._is_bundle_sale = True original_price = _ba.get_account_misc_read_val( 'price.' + self._offer_item, 9999) # For pure ticket prices we can show a percent-off. if isinstance(offer['price'], int): new_price = offer['price'] tchar = ba.charstr(SpecialChar.TICKET) original_price_str = tchar + str(original_price) new_price_str = tchar + str(new_price) percent_off = int( round(100.0 - (float(new_price) / original_price) * 100.0)) percent_off_text = ' ' + ba.Lstr( resource='store.salePercentText').evaluate().replace( '${PERCENT}', str(percent_off)) else: original_price_str = new_price_str = '?' percent_off_text = '' except Exception: print(f'Offer: {offer}') ba.print_exception('Error setting up special-offer') original_price_str = new_price_str = '?' percent_off_text = '' # If its a bundle sale, change the title. if self._is_bundle_sale: sale_text = ba.Lstr(resource='store.saleBundleText', fallback_resource='store.saleText').evaluate() else: # For full pro we say 'Upgrade?' since its not really a sale. if offer['item'] == 'pro_fullprice': sale_text = ba.Lstr( resource='store.upgradeQuestionText', fallback_resource='store.saleExclaimText').evaluate() else: sale_text = ba.Lstr( resource='store.saleExclaimText', fallback_resource='store.saleText').evaluate() self._title_text = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height - 40), size=(0, 0), text=sale_text + ((' ' + ba.Lstr(resource='store.oneTimeOnlyText').evaluate()) if self._offer['oneTimeOnly'] else '') + percent_off_text, h_align='center', v_align='center', maxwidth=self._width * 0.9 - 220, scale=1.4, color=(0.3, 1, 0.3)) self._flash_on = False self._flashing_timer: Optional[ba.Timer] = ba.Timer( 0.05, ba.WeakCall(self._flash_cycle), repeat=True, timetype=ba.TimeType.REAL) ba.timer(0.6, ba.WeakCall(self._stop_flashing), timetype=ba.TimeType.REAL) size = get_store_item_display_size(self._offer_item) display: Dict[str, Any] = {} storeitemui.instantiate_store_item_display( self._offer_item, display, parent_widget=self._root_widget, b_pos=(self._width * 0.5 - size[0] * 0.5 + 10 - ((size[0] * 0.5 + 30) if self._is_bundle_sale else 0), self._height * 0.5 - size[1] * 0.5 + 20 + (20 if self._is_bundle_sale else 0)), b_width=size[0], b_height=size[1], button=not self._is_bundle_sale) # Wire up the parts we need. if self._is_bundle_sale: self._plus_text = ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height * 0.5 + 50), size=(0, 0), text='+', h_align='center', v_align='center', maxwidth=self._width * 0.9, scale=1.4, color=(0.5, 0.5, 0.5)) self._plus_tickets = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5 + 120, self._height * 0.5 + 50), size=(0, 0), text=ba.charstr(SpecialChar.TICKET_BACKING) + str(offer['bonusTickets']), h_align='center', v_align='center', maxwidth=self._width * 0.9, scale=2.5, color=(0.2, 1, 0.2)) self._price_text = ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, 150), size=(0, 0), text=real_price, h_align='center', v_align='center', maxwidth=self._width * 0.9, scale=1.4, color=(0.2, 1, 0.2)) # Total-value if they supplied it. total_worth_item = offer.get('valueItem', None) if total_worth_item is not None: price = _ba.get_price(total_worth_item) total_worth_price = (get_clean_price(price) if price is not None else None) if total_worth_price is not None: total_worth_text = ba.Lstr(resource='store.totalWorthText', subs=[('${TOTAL_WORTH}', total_worth_price)]) self._total_worth_text = ba.textwidget( parent=self._root_widget, text=total_worth_text, position=(self._width * 0.5, 210), scale=0.9, maxwidth=self._width * 0.7, size=(0, 0), h_align='center', v_align='center', shadow=1.0, flatness=1.0, color=(0.3, 1, 1)) elif offer['item'] == 'pro_fullprice': # for full-price pro we simply show full price ba.textwidget(edit=display['price_widget'], text=real_price) ba.buttonwidget(edit=display['button'], on_activate_call=self._purchase) else: # Show old/new prices otherwise (for pro sale). ba.buttonwidget(edit=display['button'], on_activate_call=self._purchase) ba.imagewidget(edit=display['price_slash_widget'], opacity=1.0) ba.textwidget(edit=display['price_widget_left'], text=original_price_str) ba.textwidget(edit=display['price_widget_right'], text=new_price_str) # Add ticket button only if this is ticket-purchasable. if isinstance(offer.get('price'), int): self._get_tickets_button = ba.buttonwidget( parent=self._root_widget, position=(self._width - 125, self._height - 68), size=(90, 55), scale=1.0, button_type='square', color=(0.7, 0.5, 0.85), textcolor=(0.2, 1, 0.2), autoselect=True, label=ba.Lstr(resource='getTicketsWindow.titleText'), on_activate_call=self._on_get_more_tickets_press) self._ticket_text_update_timer = ba.Timer( 1.0, ba.WeakCall(self._update_tickets_text), timetype=ba.TimeType.REAL, repeat=True) self._update_tickets_text() self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True) self._cancel_button = ba.buttonwidget( parent=self._root_widget, position=(50, 40) if self._is_bundle_sale else (self._width * 0.5 - 75, 40), size=(150, 60), scale=1.0, on_activate_call=self._cancel, autoselect=True, label=ba.Lstr(resource='noThanksText')) self._cancel_countdown_text = ba.textwidget( parent=self._root_widget, text='', position=(50 + 150 + 20, 40 + 27) if self._is_bundle_sale else (self._width * 0.5 - 75 + 150 + 20, 40 + 27), scale=1.1, size=(0, 0), h_align='left', v_align='center', shadow=1.0, flatness=1.0, color=(0.6, 0.5, 0.5)) self._update_cancel_button_graphics() if self._is_bundle_sale: self._purchase_button = ba.buttonwidget( parent=self._root_widget, position=(self._width - 200, 40), size=(150, 60), scale=1.0, on_activate_call=self._purchase, autoselect=True, label=ba.Lstr(resource='store.purchaseText')) ba.containerwidget(edit=self._root_widget, cancel_button=self._cancel_button, start_button=self._purchase_button if self._is_bundle_sale else None, selected_child=self._purchase_button if self._is_bundle_sale else display['button'])
def __init__(self, origin: Sequence[float] = (0, 0)): _ba.set_party_window_open(True) self._r = 'partyWindow' self._popup_type: Optional[str] = None self._popup_party_member_client_id: Optional[int] = None self._popup_party_member_is_host: Optional[bool] = None self._width = 500 uiscale = ba.app.ui.uiscale self._height = (365 if uiscale is ba.UIScale.SMALL else 480 if uiscale is ba.UIScale.MEDIUM else 600) super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height), transition='in_scale', color=(0.40, 0.55, 0.20), parent=_ba.get_special_widget('overlay_stack'), on_outside_click_call=self.close_with_sound, scale_origin_stack_offset=origin, scale=(2.0 if uiscale is ba.UIScale.SMALL else 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else ( 240, 0) if uiscale is ba.UIScale.MEDIUM else (330, 20))) self._cancel_button = ba.buttonwidget(parent=self._root_widget, scale=0.7, position=(30, self._height - 47), size=(50, 50), label='', on_activate_call=self.close, autoselect=True, color=(0.45, 0.63, 0.15), icon=ba.gettexture('crossOut'), iconscale=1.2) ba.containerwidget(edit=self._root_widget, cancel_button=self._cancel_button) self._menu_button = ba.buttonwidget( parent=self._root_widget, scale=0.7, position=(self._width - 60, self._height - 47), size=(50, 50), label='...', autoselect=True, button_type='square', on_activate_call=ba.WeakCall(self._on_menu_button_press), color=(0.55, 0.73, 0.25), iconscale=1.2) info = _ba.get_connection_to_host_info() if info.get('name', '') != '': title = ba.Lstr(value=info['name']) else: title = ba.Lstr(resource=self._r + '.titleText') self._title_text = ba.textwidget(parent=self._root_widget, scale=0.9, color=(0.5, 0.7, 0.5), text=title, size=(0, 0), position=(self._width * 0.5, self._height - 29), maxwidth=self._width * 0.7, h_align='center', v_align='center') self._empty_str = ba.textwidget(parent=self._root_widget, scale=0.75, size=(0, 0), position=(self._width * 0.5, self._height - 65), maxwidth=self._width * 0.85, h_align='center', v_align='center') self._scroll_width = self._width - 50 self._scrollwidget = ba.scrollwidget(parent=self._root_widget, size=(self._scroll_width, self._height - 200), position=(30, 80), color=(0.4, 0.6, 0.3)) self._columnwidget = ba.columnwidget(parent=self._scrollwidget, border=2, margin=0) ba.widget(edit=self._menu_button, down_widget=self._columnwidget) self._muted_text = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height * 0.5), size=(0, 0), h_align='center', v_align='center', text=ba.Lstr(resource='chatMutedText')) self._chat_texts: list[ba.Widget] = [] # add all existing messages if chat is not muted if not ba.app.config.resolve('Chat Muted'): msgs = _ba.get_chat_messages() for msg in msgs: self._add_msg(msg) self._text_field = txt = ba.textwidget( parent=self._root_widget, editable=True, size=(530, 40), position=(44, 39), text='', maxwidth=494, shadow=0.3, flatness=1.0, description=ba.Lstr(resource=self._r + '.chatMessageText'), autoselect=True, v_align='center', corner_scale=0.7) ba.widget(edit=self._scrollwidget, autoselect=True, left_widget=self._cancel_button, up_widget=self._cancel_button, down_widget=self._text_field) ba.widget(edit=self._columnwidget, autoselect=True, up_widget=self._cancel_button, down_widget=self._text_field) ba.containerwidget(edit=self._root_widget, selected_child=txt) btn = ba.buttonwidget(parent=self._root_widget, size=(50, 35), label=ba.Lstr(resource=self._r + '.sendText'), button_type='square', autoselect=True, position=(self._width - 70, 35), on_activate_call=self._send_chat_message) ba.textwidget(edit=txt, on_return_press_call=btn.activate) self._name_widgets: list[ba.Widget] = [] self._roster: Optional[list[dict[str, Any]]] = None self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), repeat=True, timetype=ba.TimeType.REAL) self._update()
def _flash_cycle(self) -> None: if not self._root_widget: return self._flash_on = not self._flash_on ba.textwidget(edit=self._title_text, color=(0.3, 1, 0.3) if self._flash_on else (1, 0.5, 0))
def _send_chat_message(self) -> None: _ba.chatmessage(cast(str, ba.textwidget(query=self._text_field))) ba.textwidget(edit=self._text_field, text='')
def _on_max_public_party_size_minus_press(self) -> None: val = max(1, _ba.get_public_party_max_size() - 1) _ba.set_public_party_max_size(val) ba.textwidget(edit=self._host_max_party_size_value, text=str(val))
def __init__(self, transition: str = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-statements from ba.internal import master_server_get import threading # Preload some modules we use in a background thread so we won't # have a visual hitch when the user taps them. threading.Thread(target=self._preload_modules).start() app = ba.app # If they provided an origin-widget, scale up from that. scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None uiscale = ba.app.ui.uiscale self._width = 870.0 if uiscale is ba.UIScale.SMALL else 670.0 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = (390.0 if uiscale is ba.UIScale.SMALL else 450.0 if uiscale is ba.UIScale.MEDIUM else 520.0) self._spacing = 32 self._menu_open = False top_extra = 10 if uiscale is ba.UIScale.SMALL else 0 super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height + top_extra), transition=transition, toolbar_visibility='menu_minimal', scale_origin_stack_offset=scale_origin, scale=(2.06 if uiscale is ba.UIScale.SMALL else 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0))) self._prev_lang = '' self._prev_lang_list: list[str] = [] self._complete_langs_list: Optional[list] = None self._complete_langs_error = False self._language_popup: Optional[popup_ui.PopupMenu] = None # In vr-mode, the internal keyboard is currently the *only* option, # so no need to show this. self._show_always_use_internal_keyboard = (not app.vr_mode and not app.iircade_mode) self._scroll_width = self._width - (100 + 2 * x_inset) self._scroll_height = self._height - 115.0 self._sub_width = self._scroll_width * 0.95 self._sub_height = 724.0 if self._show_always_use_internal_keyboard: self._sub_height += 62 self._show_disable_gyro = app.platform in {'ios', 'android'} if self._show_disable_gyro: self._sub_height += 42 self._do_vr_test_button = app.vr_mode self._do_net_test_button = True self._extra_button_spacing = self._spacing * 2.5 if self._do_vr_test_button: self._sub_height += self._extra_button_spacing if self._do_net_test_button: self._sub_height += self._extra_button_spacing self._sub_height += self._spacing * 2.0 # plugins self._r = 'settingsWindowAdvanced' if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: ba.containerwidget(edit=self._root_widget, on_cancel_call=self._do_back) self._back_button = None else: self._back_button = ba.buttonwidget( parent=self._root_widget, position=(53 + x_inset, self._height - 60), size=(140, 60), scale=0.8, autoselect=True, label=ba.Lstr(resource='backText'), button_type='back', on_activate_call=self._do_back) ba.containerwidget(edit=self._root_widget, cancel_button=self._back_button) self._title_text = ba.textwidget(parent=self._root_widget, position=(0, self._height - 52), size=(self._width, 25), text=ba.Lstr(resource=self._r + '.titleText'), color=app.ui.title_color, h_align='center', v_align='top') if self._back_button is not None: ba.buttonwidget(edit=self._back_button, button_type='backSmall', size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) self._scrollwidget = ba.scrollwidget(parent=self._root_widget, position=(50 + x_inset, 50), simple_culling_v=20.0, highlight=False, size=(self._scroll_width, self._scroll_height), selection_loops_to_parent=True) ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) self._subcontainer = ba.containerwidget(parent=self._scrollwidget, size=(self._sub_width, self._sub_height), background=False, selection_loops_to_parent=True) self._rebuild() # Rebuild periodically to pick up language changes/additions/etc. self._rebuild_timer = ba.Timer(1.0, ba.WeakCall(self._rebuild), repeat=True, timetype=ba.TimeType.REAL) # Fetch the list of completed languages. master_server_get('bsLangGetCompleted', {'b': app.build_number}, callback=ba.WeakCall(self._completed_langs_cb))
def on_activate( self, parent_widget: ba.Widget, tab_button: ba.Widget, region_width: float, region_height: float, region_left: float, region_bottom: float, ) -> ba.Widget: c_width = region_width c_height = region_height - 20 self._container = ba.containerwidget( parent=parent_widget, position=(region_left, region_bottom + (region_height - c_height) * 0.5), size=(c_width, c_height), background=False, selection_loops_to_parent=True) v = c_height - 30 self._join_text = ba.textwidget( parent=self._container, position=(c_width * 0.5 - 245, v - 13), color=(0.6, 1.0, 0.6), scale=1.3, size=(200, 30), maxwidth=250, h_align='left', v_align='center', click_activate=True, selectable=True, autoselect=True, on_activate_call=lambda: self._set_sub_tab( SubTabType.JOIN, region_width, region_height, playsound=True, ), text=ba.Lstr(resource='gatherWindow.' 'joinPublicPartyDescriptionText')) self._host_text = ba.textwidget( parent=self._container, position=(c_width * 0.5 + 45, v - 13), color=(0.6, 1.0, 0.6), scale=1.3, size=(200, 30), maxwidth=250, h_align='left', v_align='center', click_activate=True, selectable=True, autoselect=True, on_activate_call=lambda: self._set_sub_tab( SubTabType.HOST, region_width, region_height, playsound=True, ), text=ba.Lstr(resource='gatherWindow.' 'hostPublicPartyDescriptionText')) ba.widget(edit=self._join_text, up_widget=tab_button) ba.widget(edit=self._host_text, left_widget=self._join_text, up_widget=tab_button) ba.widget(edit=self._join_text, right_widget=self._host_text) # Attempt to fetch our local address so we have it for error messages. if self._local_address is None: AddrFetchThread(ba.WeakCall(self._fetch_local_addr_cb)).start() self._set_sub_tab(self._sub_tab, region_width, region_height) self._update_timer = ba.Timer(0.1, ba.WeakCall(self._update), repeat=True, timetype=ba.TimeType.REAL) return self._container
def __init__(self, position: Tuple[float, float], scale: float = None): # pylint: disable=too-many-locals uiscale = ba.app.ui.uiscale if scale is None: scale = (2.3 if uiscale is ba.UIScale.SMALL else 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) self._transitioning_out = False self._width = 450 self._height = (300 if uiscale is ba.UIScale.SMALL else 370 if uiscale is ba.UIScale.MEDIUM else 450) bg_color = (0.5, 0.4, 0.6) # creates our _root_widget popup.PopupWindow.__init__(self, position=position, size=(self._width, self._height), scale=scale, bg_color=bg_color) self._cancel_button = ba.buttonwidget( parent=self.root_widget, position=(50, self._height - 30), size=(50, 50), scale=0.5, label='', color=bg_color, on_activate_call=self._on_cancel_press, autoselect=True, icon=ba.gettexture('crossOut'), iconscale=1.2) achievements = ba.app.ach.achievements num_complete = len([a for a in achievements if a.complete]) txt_final = ba.Lstr( resource='accountSettingsWindow.achievementProgressText', subs=[('${COUNT}', str(num_complete)), ('${TOTAL}', str(len(achievements)))]) self._title_text = ba.textwidget(parent=self.root_widget, position=(self._width * 0.5, self._height - 20), size=(0, 0), h_align='center', v_align='center', scale=0.6, text=txt_final, maxwidth=200, color=(1, 1, 1, 0.4)) self._scrollwidget = ba.scrollwidget(parent=self.root_widget, size=(self._width - 60, self._height - 70), position=(30, 30), capture_arrows=True, simple_culling_v=10) ba.widget(edit=self._scrollwidget, autoselect=True) ba.containerwidget(edit=self.root_widget, cancel_button=self._cancel_button) incr = 36 sub_width = self._width - 90 sub_height = 40 + len(achievements) * incr eq_rsrc = 'coopSelectWindow.powerRankingPointsEqualsText' pts_rsrc = 'coopSelectWindow.powerRankingPointsText' self._subcontainer = ba.containerwidget(parent=self._scrollwidget, size=(sub_width, sub_height), background=False) total_pts = 0 for i, ach in enumerate(achievements): complete = ach.complete ba.textwidget(parent=self._subcontainer, position=(sub_width * 0.08 - 5, sub_height - 20 - incr * i), maxwidth=20, scale=0.5, color=(0.6, 0.6, 0.7) if complete else (0.6, 0.6, 0.7, 0.2), flatness=1.0, shadow=0.0, text=str(i + 1), size=(0, 0), h_align='right', v_align='center') ba.imagewidget(parent=self._subcontainer, position=(sub_width * 0.10 + 1, sub_height - 20 - incr * i - 9) if complete else (sub_width * 0.10 - 4, sub_height - 20 - incr * i - 14), size=(18, 18) if complete else (27, 27), opacity=1.0 if complete else 0.3, color=ach.get_icon_color(complete)[:3], texture=ach.get_icon_texture(complete)) if complete: ba.imagewidget(parent=self._subcontainer, position=(sub_width * 0.10 - 4, sub_height - 25 - incr * i - 9), size=(28, 28), color=(2, 1.4, 0), texture=ba.gettexture('achievementOutline')) ba.textwidget(parent=self._subcontainer, position=(sub_width * 0.19, sub_height - 19 - incr * i + 3), maxwidth=sub_width * 0.62, scale=0.6, flatness=1.0, shadow=0.0, color=(1, 1, 1) if complete else (1, 1, 1, 0.2), text=ach.display_name, size=(0, 0), h_align='left', v_align='center') ba.textwidget(parent=self._subcontainer, position=(sub_width * 0.19, sub_height - 19 - incr * i - 10), maxwidth=sub_width * 0.62, scale=0.4, flatness=1.0, shadow=0.0, color=(0.83, 0.8, 0.85) if complete else (0.8, 0.8, 0.8, 0.2), text=ach.description_full_complete if complete else ach.description_full, size=(0, 0), h_align='left', v_align='center') pts = ach.power_ranking_value ba.textwidget(parent=self._subcontainer, position=(sub_width * 0.92, sub_height - 20 - incr * i), maxwidth=sub_width * 0.15, color=(0.7, 0.8, 1.0) if complete else (0.9, 0.9, 1.0, 0.3), flatness=1.0, shadow=0.0, scale=0.6, text=ba.Lstr(resource=pts_rsrc, subs=[('${NUMBER}', str(pts))]), size=(0, 0), h_align='center', v_align='center') if complete: total_pts += pts ba.textwidget(parent=self._subcontainer, position=(sub_width * 1.0, sub_height - 20 - incr * len(achievements)), maxwidth=sub_width * 0.5, scale=0.7, color=(0.7, 0.8, 1.0), flatness=1.0, shadow=0.0, text=ba.Lstr( value='${A} ${B}', subs=[ ('${A}', ba.Lstr(resource='coopSelectWindow.totalText')), ('${B}', ba.Lstr(resource=eq_rsrc, subs=[('${NUMBER}', str(total_pts))])) ]), size=(0, 0), h_align='right', v_align='center')