def on_begin(self) -> None: from ba.deprecated import get_resource ba.set_analytics_screen('Teams Score Screen') super().on_begin() height = 130 active_team_count = len(self.teams) vval = (height * active_team_count) / 2 - height / 2 i = 0 shift_time = 2.5 # Usually we say 'Best of 7', but if the language prefers we can say # 'First to 4'. session = self.session assert isinstance(session, ba.TeamBaseSession) if get_resource('bestOfUseFirstToInstead'): best_txt = ba.Lstr(resource='firstToSeriesText', subs=[('${COUNT}', str(session.get_series_length() / 2 + 1)) ]) else: best_txt = ba.Lstr(resource='bestOfSeriesText', subs=[('${COUNT}', str(session.get_series_length()))]) ZoomText(best_txt, position=(0, 175), shiftposition=(-250, 175), shiftdelay=2.5, flash=False, trail=False, h_align='center', scale=0.25, color=(0.5, 0.5, 0.5, 1.0), jitter=3.0).autoretain() for team in self.teams: ba.timer( i * 0.15 + 0.15, ba.WeakCall(self._show_team_name, vval - i * height, team, i * 0.2, shift_time - (i * 0.150 + 0.150))) ba.timer(i * 0.150 + 0.5, ba.Call(ba.playsound, self._score_display_sound_small)) scored = (team is self.settings['winner']) delay = 0.2 if scored: delay = 1.2 ba.timer( i * 0.150 + 0.2, ba.WeakCall(self._show_team_old_score, vval - i * height, team, shift_time - (i * 0.15 + 0.2))) ba.timer(i * 0.15 + 1.5, ba.Call(ba.playsound, self._score_display_sound)) ba.timer( i * 0.150 + delay, ba.WeakCall(self._show_team_score, vval - i * height, team, scored, i * 0.2 + 0.1, shift_time - (i * 0.15 + delay))) i += 1 self.show_player_scores()
def _email(self) -> None: import urllib.parse # If somehow we got signed out. if _ba.get_account_state() != 'signed_in': ba.screenmessage(ba.Lstr(resource='notSignedInText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return ba.set_analytics_screen('Email Friend Code') subject = (ba.Lstr(resource='gatherWindow.friendHasSentPromoCodeText'). evaluate().replace( '${NAME}', _ba.get_account_name()).replace( '${APP_NAME}', ba.Lstr(resource='titleText').evaluate()).replace( '${COUNT}', str(self._data['tickets']))) body = (ba.Lstr(resource='gatherWindow.youHaveBeenSentAPromoCodeText'). evaluate().replace('${APP_NAME}', ba.Lstr(resource='titleText').evaluate()) + '\n\n' + str(self._data['code']) + '\n\n') body += ( (ba.Lstr(resource='gatherWindow.friendPromoCodeRedeemShortText'). evaluate().replace('${COUNT}', str(self._data['tickets']))) + '\n\n' + ba.Lstr(resource='gatherWindow.friendPromoCodeInstructionsText'). evaluate().replace('${APP_NAME}', ba.Lstr(resource='titleText').evaluate()) + '\n' + ba.Lstr(resource='gatherWindow.friendPromoCodeExpireText'). evaluate().replace('${EXPIRE_HOURS}', str( self._data['expireHours'])) + '\n' + ba.Lstr(resource='enjoyText').evaluate()) ba.open_url('mailto:?subject=' + urllib.parse.quote(subject) + '&body=' + urllib.parse.quote(body))
def __init__(self, transition: Optional[str] = 'in_right'): # pylint: disable=cyclic-import import threading from bastd.mainmenu import MainMenuSession self._in_game = not isinstance(_ba.get_foreground_host_session(), MainMenuSession) # 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() if not self._in_game: ba.set_analytics_screen('Main Menu') self._show_remote_app_info_on_first_launch() # Make a vanilla container; we'll modify it to our needs in refresh. super().__init__(root_widget=ba.containerwidget( transition=transition, toolbar_visibility='menu_minimal_no_back' if self. _in_game else 'menu_minimal_no_back')) # Grab this stuff in case it changes. self._is_demo = ba.app.demo_mode self._is_arcade = ba.app.arcade_mode self._is_iircade = ba.app.iircade_mode self._tdelay = 0.0 self._t_delay_inc = 0.02 self._t_delay_play = 1.7 self._p_index = 0 self._use_autoselect = True self._button_width = 200.0 self._button_height = 45.0 self._width = 100.0 self._height = 100.0 self._demo_menu_button: Optional[ba.Widget] = None self._gather_button: Optional[ba.Widget] = None self._start_button: Optional[ba.Widget] = None self._watch_button: Optional[ba.Widget] = None self._gc_button: Optional[ba.Widget] = None self._how_to_play_button: Optional[ba.Widget] = None self._credits_button: Optional[ba.Widget] = None self._settings_button: Optional[ba.Widget] = None self._store_char_tex = self._get_store_char_tex() self._refresh() self._restore_state() # Keep an eye on a few things and refresh if they change. self._account_state = _ba.get_account_state() self._account_state_num = _ba.get_account_state_num() self._account_type = (_ba.get_account_type() if self._account_state == 'signed_in' else None) self._refresh_timer = ba.Timer(1.0, ba.WeakCall(self._check_refresh), repeat=True, timetype=ba.TimeType.REAL)
def _google_invites(self) -> None: ba.set_analytics_screen('App Invite UI') _ba.show_app_invite( ba.Lstr(resource='gatherWindow.appInviteTitleText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) ]).evaluate(), ba.Lstr(resource='gatherWindow.appInviteMessageText', subs=[('${COUNT}', str(self._data['tickets'])), ('${NAME}', _ba.get_account_name().split()[0]), ('${APP_NAME}', ba.Lstr(resource='titleText')) ]).evaluate(), self._data['code'])
def on_begin(self) -> None: ba.set_analytics_screen('Draw Score Screen') super().on_begin() ZoomText(ba.Lstr(resource='drawText'), position=(0, 0), maxwidth=400, shiftposition=(-220, 0), shiftdelay=2.0, flash=False, trail=False, jitter=1.0).autoretain() ba.timer(0.35, ba.Call(ba.playsound, self._score_display_sound)) self.show_player_scores(results=self.settings_raw.get('results', None))
def __init__(self, transition: Optional[str] = 'in_right'): # pylint: disable=cyclic-import from bastd import mainmenu self._in_game = not isinstance(_ba.get_foreground_host_session(), mainmenu.MainMenuSession) if not self._in_game: ba.set_analytics_screen('Main Menu') self._show_remote_app_info_on_first_launch() # Make a vanilla container; we'll modify it to our needs in refresh. super().__init__(root_widget=ba.containerwidget( transition=transition, toolbar_visibility='menu_minimal_no_back' if self. _in_game else 'menu_minimal_no_back')) self._is_kiosk = ba.app.kiosk_mode self._tdelay = 0.0 self._t_delay_inc = 0.02 self._t_delay_play = 1.7 self._p_index = 0 self._use_autoselect = True self._button_width = 200.0 self._button_height = 45.0 self._width = 100.0 self._height = 100.0 self._demo_menu_button: Optional[ba.Widget] = None self._gather_button: Optional[ba.Widget] = None self._start_button: Optional[ba.Widget] = None self._watch_button: Optional[ba.Widget] = None self._gc_button: Optional[ba.Widget] = None self._how_to_play_button: Optional[ba.Widget] = None self._credits_button: Optional[ba.Widget] = None self._store_char_tex = self._get_store_char_tex() self._refresh() self._restore_state() # Keep an eye on a few things and refresh if they change. self._account_state = _ba.get_account_state() self._account_state_num = _ba.get_account_state_num() self._account_type = (_ba.get_account_type() if self._account_state == 'signed_in' else None) self._refresh_timer = ba.Timer(1.0, ba.WeakCall(self._check_refresh), repeat=True, timetype=ba.TimeType.REAL)
def _google_invites(self) -> None: if self._data is None: ba.screenmessage(ba.Lstr( resource='getTicketsWindow.unavailableTemporarilyText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return if _ba.get_account_state() == 'signed_in': ba.set_analytics_screen('App Invite UI') _ba.show_app_invite( ba.Lstr(resource='gatherWindow.appInviteTitleText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) ]).evaluate(), ba.Lstr(resource='gatherWindow.appInviteMessageText', subs=[('${COUNT}', str(self._data['tickets'])), ('${NAME}', _ba.get_account_name().split()[0]), ('${APP_NAME}', ba.Lstr(resource='titleText')) ]).evaluate(), self._data['code']) else: ba.playsound(ba.getsound('error'))
def __init__(self, transition: str = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # pylint: disable=too-many-locals 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() ba.set_analytics_screen('Settings Window') 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.uiscale width = 900 if uiscale is ba.UIScale.SMALL else 580 x_inset = 75 if uiscale is ba.UIScale.SMALL else 0 height = 435 # button_height = 42 self._r = 'settingsWindow' top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 uiscale = ba.app.uiscale super().__init__(root_widget=ba.containerwidget( size=(width, height + top_extra), transition=transition, toolbar_visibility='menu_minimal', scale_origin_stack_offset=scale_origin, scale=(1.75 if uiscale is ba.UIScale.SMALL else 1.35 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: self._back_button = None ba.containerwidget(edit=self._root_widget, on_cancel_call=self._do_back) else: self._back_button = btn = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(40 + x_inset, height - 55), size=(130, 60), scale=0.8, text_scale=1.2, label=ba.Lstr(resource='backText'), button_type='back', on_activate_call=self._do_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.title_color, h_align='center', v_align='center', maxwidth=130) 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)) v = height - 80 v -= 145 basew = 280 if uiscale is ba.UIScale.SMALL else 230 baseh = 170 x_offs = x_inset + (105 if uiscale is ba.UIScale.SMALL else 72) - basew # now unused x_offs2 = x_offs + basew - 7 x_offs3 = x_offs + 2 * (basew - 7) x_offs4 = x_offs2 x_offs5 = x_offs3 def _b_title(x: float, y: float, button: ba.Widget, text: Union[str, ba.Lstr]) -> None: ba.textwidget(parent=self._root_widget, text=text, position=(x + basew * 0.47, y + baseh * 0.22), maxwidth=basew * 0.7, size=(0, 0), h_align='center', v_align='center', draw_controller=button, color=(0.7, 0.9, 0.7, 1.0)) ctb = self._controllers_button = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(x_offs2, v), size=(basew, baseh), button_type='square', label='', on_activate_call=self._do_controllers) if ba.app.toolbars and self._back_button is None: bbtn = _ba.get_special_widget('back_button') ba.widget(edit=ctb, left_widget=bbtn) _b_title(x_offs2, v, ctb, ba.Lstr(resource=self._r + '.controllersText')) imgw = imgh = 130 ba.imagewidget(parent=self._root_widget, position=(x_offs2 + basew * 0.49 - imgw * 0.5, v + 35), size=(imgw, imgh), texture=ba.gettexture('controllerIcon'), draw_controller=ctb) gfxb = self._graphics_button = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(x_offs3, v), size=(basew, baseh), button_type='square', label='', on_activate_call=self._do_graphics) if ba.app.toolbars: pbtn = _ba.get_special_widget('party_button') ba.widget(edit=gfxb, up_widget=pbtn, right_widget=pbtn) _b_title(x_offs3, v, gfxb, ba.Lstr(resource=self._r + '.graphicsText')) imgw = imgh = 110 ba.imagewidget(parent=self._root_widget, position=(x_offs3 + basew * 0.49 - imgw * 0.5, v + 42), size=(imgw, imgh), texture=ba.gettexture('graphicsIcon'), draw_controller=gfxb) v -= (baseh - 5) abtn = self._audio_button = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(x_offs4, v), size=(basew, baseh), button_type='square', label='', on_activate_call=self._do_audio) _b_title(x_offs4, v, abtn, ba.Lstr(resource=self._r + '.audioText')) imgw = imgh = 120 ba.imagewidget(parent=self._root_widget, position=(x_offs4 + basew * 0.49 - imgw * 0.5 + 5, v + 35), size=(imgw, imgh), color=(1, 1, 0), texture=ba.gettexture('audioIcon'), draw_controller=abtn) avb = self._advanced_button = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(x_offs5, v), size=(basew, baseh), button_type='square', label='', on_activate_call=self._do_advanced) _b_title(x_offs5, v, avb, ba.Lstr(resource=self._r + '.advancedText')) imgw = imgh = 120 ba.imagewidget(parent=self._root_widget, position=(x_offs5 + basew * 0.49 - imgw * 0.5 + 5, v + 35), size=(imgw, imgh), color=(0.8, 0.95, 1), texture=ba.gettexture('advancedIcon'), draw_controller=avb)
def __init__(self, main_menu: bool = False, origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # pylint: disable=too-many-locals from ba.internal import get_remote_app_name from ba.deprecated import get_resource ba.set_analytics_screen('Help 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' self._r = 'helpWindow' self._main_menu = main_menu width = 950 if ba.app.small_ui else 750 x_offs = 100 if ba.app.small_ui else 0 height = 460 if ba.app.small_ui else 530 if ba.app.med_ui else 600 super().__init__(root_widget=ba.containerwidget( size=(width, height), transition=transition, toolbar_visibility='menu_minimal', scale_origin_stack_offset=scale_origin, scale=( 1.77 if ba.app.small_ui else 1.25 if ba.app.med_ui else 1.0), stack_offset=(0, -30) if ba.app.small_ui else ( 0, 15) if ba.app.med_ui else (0, 0))) ba.textwidget(parent=self._root_widget, position=(0, height - (50 if ba.app.small_ui else 45)), size=(width, 25), text=ba.Lstr(resource=self._r + '.titleText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))]), color=ba.app.title_color, h_align='center', v_align='top') self._scrollwidget = ba.scrollwidget( parent=self._root_widget, position=(44 + x_offs, 55 if ba.app.small_ui else 55), simple_culling_v=100.0, size=(width - (88 + 2 * x_offs), height - 120 + (5 if ba.app.small_ui else 0)), capture_arrows=True) if ba.app.toolbars: ba.widget(edit=self._scrollwidget, right_widget=_ba.get_special_widget('party_button')) ba.containerwidget(edit=self._root_widget, selected_child=self._scrollwidget) # ugly: create this last so it gets first dibs at touch events (since # we have it close to the scroll widget) if ba.app.small_ui and ba.app.toolbars: ba.containerwidget(edit=self._root_widget, on_cancel_call=self._close) ba.widget(edit=self._scrollwidget, left_widget=_ba.get_special_widget('back_button')) else: btn = ba.buttonwidget( parent=self._root_widget, position=(x_offs + (40 + 0 if ba.app.small_ui else 70), height - (59 if ba.app.small_ui else 50)), size=(140, 60), scale=0.7 if ba.app.small_ui else 0.8, label=ba.Lstr( resource='backText') if self._main_menu else 'Close', button_type='back' if self._main_menu else None, extra_touch_border_scale=2.0, autoselect=True, on_activate_call=self._close) ba.containerwidget(edit=self._root_widget, cancel_button=btn) if self._main_menu: ba.buttonwidget(edit=btn, button_type='backSmall', size=(60, 55), label=ba.charstr(ba.SpecialChar.BACK)) # interface_type = ba.app.interface_type self._sub_width = 660 self._sub_height = 1590 + get_resource( self._r + '.someDaysExtraSpace') + get_resource( self._r + '.orPunchingSomethingExtraSpace') self._subcontainer = ba.containerwidget(parent=self._scrollwidget, size=(self._sub_width, self._sub_height), background=False, claims_left_right=False, claims_tab=False) spacing = 1.0 h = self._sub_width * 0.5 v = self._sub_height - 55 logo_tex = ba.gettexture('logo') icon_buffer = 1.1 header = (0.7, 1.0, 0.7, 1.0) header2 = (0.8, 0.8, 1.0, 1.0) paragraph = (0.8, 0.8, 1.0, 1.0) txt = ba.Lstr(resource=self._r + '.welcomeText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) ]).evaluate() txt_scale = 1.4 txt_maxwidth = 480 ba.textwidget(parent=self._subcontainer, position=(h, v), size=(0, 0), scale=txt_scale, flatness=0.5, res_scale=1.5, text=txt, h_align='center', color=header, v_align='center', maxwidth=txt_maxwidth) txt_width = min( txt_maxwidth, _ba.get_string_width(txt, suppress_warning=True) * txt_scale) icon_size = 70 hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer) ba.imagewidget(parent=self._subcontainer, size=(icon_size, icon_size), position=(hval2 - 0.5 * icon_size, v - 0.45 * icon_size), texture=logo_tex) force_test = False app = ba.app if (app.platform == 'android' and app.subplatform == 'alibaba') or force_test: v -= 120.0 txtv = ( '\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\x80\xe4\xb8\xaa\xe5\x8f\xaf' '\xe4\xbb\xa5\xe5\x92\x8c\xe5\xae\xb6\xe4\xba\xba\xe6\x9c\x8b' '\xe5\x8f\x8b\xe4\xb8\x80\xe8\xb5\xb7\xe7\x8e\xa9\xe7\x9a\x84' '\xe6\xb8\xb8\xe6\x88\x8f,\xe5\x90\x8c\xe6\x97\xb6\xe6\x94\xaf' '\xe6\x8c\x81\xe8\x81\x94 \xe2\x80\xa8\xe7\xbd\x91\xe5\xaf\xb9' '\xe6\x88\x98\xe3\x80\x82\n' '\xe5\xa6\x82\xe6\xb2\xa1\xe6\x9c\x89\xe6\xb8\xb8\xe6\x88\x8f' '\xe6\x89\x8b\xe6\x9f\x84,\xe5\x8f\xaf\xe4\xbb\xa5\xe4\xbd\xbf' '\xe7\x94\xa8\xe7\xa7\xbb\xe5\x8a\xa8\xe8\xae\xbe\xe5\xa4\x87' '\xe6\x89\xab\xe7\xa0\x81\xe4\xb8\x8b\xe8\xbd\xbd\xe2\x80\x9c' '\xe9\x98\xbf\xe9\x87\x8c\xc2' '\xa0TV\xc2\xa0\xe5\x8a\xa9\xe6\x89' '\x8b\xe2\x80\x9d\xe7\x94\xa8 \xe6\x9d\xa5\xe4\xbb\xa3\xe6\x9b' '\xbf\xe5\xa4\x96\xe8\xae\xbe\xe3\x80\x82\n' '\xe6\x9c\x80\xe5\xa4\x9a\xe6\x94\xaf\xe6\x8c\x81\xe6\x8e\xa5' '\xe5\x85\xa5\xc2\xa08\xc2\xa0\xe4\xb8\xaa\xe5\xa4\x96\xe8' '\xae\xbe') ba.textwidget(parent=self._subcontainer, size=(0, 0), h_align='center', v_align='center', maxwidth=self._sub_width * 0.9, position=(self._sub_width * 0.5, v - 180), text=txtv) ba.imagewidget(parent=self._subcontainer, position=(self._sub_width - 320, v - 120), size=(200, 200), texture=ba.gettexture('aliControllerQR')) ba.imagewidget(parent=self._subcontainer, position=(90, v - 130), size=(210, 210), texture=ba.gettexture('multiplayerExamples')) v -= 120.0 else: v -= spacing * 50.0 txt = ba.Lstr(resource=self._r + '.someDaysText').evaluate() ba.textwidget(parent=self._subcontainer, position=(h, v), size=(0, 0), scale=1.2, maxwidth=self._sub_width * 0.9, text=txt, h_align='center', color=paragraph, v_align='center', flatness=1.0) v -= (spacing * 25.0 + get_resource(self._r + '.someDaysExtraSpace')) txt_scale = 0.66 txt = ba.Lstr(resource=self._r + '.orPunchingSomethingText').evaluate() ba.textwidget(parent=self._subcontainer, position=(h, v), size=(0, 0), scale=txt_scale, maxwidth=self._sub_width * 0.9, text=txt, h_align='center', color=paragraph, v_align='center', flatness=1.0) v -= (spacing * 27.0 + get_resource(self._r + '.orPunchingSomethingExtraSpace')) txt_scale = 1.0 txt = ba.Lstr(resource=self._r + '.canHelpText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) ]).evaluate() ba.textwidget(parent=self._subcontainer, position=(h, v), size=(0, 0), scale=txt_scale, flatness=1.0, text=txt, h_align='center', color=paragraph, v_align='center') v -= spacing * 70.0 txt_scale = 1.0 txt = ba.Lstr(resource=self._r + '.toGetTheMostText').evaluate() ba.textwidget(parent=self._subcontainer, position=(h, v), size=(0, 0), scale=txt_scale, maxwidth=self._sub_width * 0.9, text=txt, h_align='center', color=header, v_align='center', flatness=1.0) v -= spacing * 40.0 txt_scale = 0.74 txt = ba.Lstr(resource=self._r + '.friendsText').evaluate() hval2 = h - 220 ba.textwidget(parent=self._subcontainer, position=(hval2, v), size=(0, 0), scale=txt_scale, maxwidth=100, text=txt, h_align='right', color=header, v_align='center', flatness=1.0) txt = ba.Lstr(resource=self._r + '.friendsGoodText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) ]).evaluate() txt_scale = 0.7 ba.textwidget(parent=self._subcontainer, position=(hval2 + 10, v + 8), size=(0, 0), scale=txt_scale, maxwidth=500, text=txt, h_align='left', color=paragraph, flatness=1.0) app = ba.app v -= spacing * 45.0 txt = (ba.Lstr(resource=self._r + '.devicesText').evaluate() if app.vr_mode else ba.Lstr(resource=self._r + '.controllersText').evaluate()) txt_scale = 0.74 hval2 = h - 220 ba.textwidget(parent=self._subcontainer, position=(hval2, v), size=(0, 0), scale=txt_scale, maxwidth=100, text=txt, h_align='right', color=header, v_align='center', flatness=1.0) txt_scale = 0.7 if not app.vr_mode: txt = ba.Lstr(resource=self._r + '.controllersInfoText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText')), ('${REMOTE_APP_NAME}', get_remote_app_name())]).evaluate() else: txt = ba.Lstr(resource=self._r + '.devicesInfoText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) ]).evaluate() ba.textwidget(parent=self._subcontainer, position=(hval2 + 10, v + 8), size=(0, 0), scale=txt_scale, maxwidth=500, max_height=105, text=txt, h_align='left', color=paragraph, flatness=1.0) v -= spacing * 150.0 txt = ba.Lstr(resource=self._r + '.controlsText').evaluate() txt_scale = 1.4 txt_maxwidth = 480 ba.textwidget(parent=self._subcontainer, position=(h, v), size=(0, 0), scale=txt_scale, flatness=0.5, text=txt, h_align='center', color=header, v_align='center', res_scale=1.5, maxwidth=txt_maxwidth) txt_width = min( txt_maxwidth, _ba.get_string_width(txt, suppress_warning=True) * txt_scale) icon_size = 70 hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer) ba.imagewidget(parent=self._subcontainer, size=(icon_size, icon_size), position=(hval2 - 0.5 * icon_size, v - 0.45 * icon_size), texture=logo_tex) v -= spacing * 45.0 txt_scale = 0.7 txt = ba.Lstr(resource=self._r + '.controlsSubtitleText', subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) ]).evaluate() ba.textwidget(parent=self._subcontainer, position=(h, v), size=(0, 0), scale=txt_scale, maxwidth=self._sub_width * 0.9, flatness=1.0, text=txt, h_align='center', color=paragraph, v_align='center') v -= spacing * 160.0 sep = 70 icon_size = 100 # icon_size_2 = 30 hval2 = h - sep vval2 = v ba.imagewidget(parent=self._subcontainer, size=(icon_size, icon_size), position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size), texture=ba.gettexture('buttonPunch'), color=(1, 0.7, 0.3)) txt_scale = get_resource(self._r + '.punchInfoTextScale') txt = ba.Lstr(resource=self._r + '.punchInfoText').evaluate() ba.textwidget(parent=self._subcontainer, position=(h - sep - 185 + 70, v + 120), size=(0, 0), scale=txt_scale, flatness=1.0, text=txt, h_align='center', color=(1, 0.7, 0.3, 1.0), v_align='top') hval2 = h + sep vval2 = v ba.imagewidget(parent=self._subcontainer, size=(icon_size, icon_size), position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size), texture=ba.gettexture('buttonBomb'), color=(1, 0.3, 0.3)) txt = ba.Lstr(resource=self._r + '.bombInfoText').evaluate() txt_scale = get_resource(self._r + '.bombInfoTextScale') ba.textwidget(parent=self._subcontainer, position=(h + sep + 50 + 60, v - 35), size=(0, 0), scale=txt_scale, flatness=1.0, maxwidth=270, text=txt, h_align='center', color=(1, 0.3, 0.3, 1.0), v_align='top') hval2 = h vval2 = v + sep ba.imagewidget(parent=self._subcontainer, size=(icon_size, icon_size), position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size), texture=ba.gettexture('buttonPickUp'), color=(0.5, 0.5, 1)) txtl = ba.Lstr(resource=self._r + '.pickUpInfoText') txt_scale = get_resource(self._r + '.pickUpInfoTextScale') ba.textwidget(parent=self._subcontainer, position=(h + 60 + 120, v + sep + 50), size=(0, 0), scale=txt_scale, flatness=1.0, text=txtl, h_align='center', color=(0.5, 0.5, 1, 1.0), v_align='top') hval2 = h vval2 = v - sep ba.imagewidget(parent=self._subcontainer, size=(icon_size, icon_size), position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size), texture=ba.gettexture('buttonJump'), color=(0.4, 1, 0.4)) txt = ba.Lstr(resource=self._r + '.jumpInfoText').evaluate() txt_scale = get_resource(self._r + '.jumpInfoTextScale') ba.textwidget(parent=self._subcontainer, position=(h - 250 + 75, v - sep - 15 + 30), size=(0, 0), scale=txt_scale, flatness=1.0, text=txt, h_align='center', color=(0.4, 1, 0.4, 1.0), v_align='top') txt = ba.Lstr(resource=self._r + '.runInfoText').evaluate() txt_scale = get_resource(self._r + '.runInfoTextScale') ba.textwidget(parent=self._subcontainer, position=(h, v - sep - 100), size=(0, 0), scale=txt_scale, maxwidth=self._sub_width * 0.93, flatness=1.0, text=txt, h_align='center', color=(0.7, 0.7, 1.0, 1.0), v_align='center') v -= spacing * 280.0 txt = ba.Lstr(resource=self._r + '.powerupsText').evaluate() txt_scale = 1.4 txt_maxwidth = 480 ba.textwidget(parent=self._subcontainer, position=(h, v), size=(0, 0), scale=txt_scale, flatness=0.5, text=txt, h_align='center', color=header, v_align='center', maxwidth=txt_maxwidth) txt_width = min( txt_maxwidth, _ba.get_string_width(txt, suppress_warning=True) * txt_scale) icon_size = 70 hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer) ba.imagewidget(parent=self._subcontainer, size=(icon_size, icon_size), position=(hval2 - 0.5 * icon_size, v - 0.45 * icon_size), texture=logo_tex) v -= spacing * 50.0 txt_scale = get_resource(self._r + '.powerupsSubtitleTextScale') txt = ba.Lstr(resource=self._r + '.powerupsSubtitleText').evaluate() ba.textwidget(parent=self._subcontainer, position=(h, v), size=(0, 0), scale=txt_scale, maxwidth=self._sub_width * 0.9, text=txt, h_align='center', color=paragraph, v_align='center', flatness=1.0) v -= spacing * 1.0 mm1 = -270 mm2 = -215 mm3 = 0 icon_size = 50 shadow_size = 80 shadow_offs_x = 3 shadow_offs_y = -4 t_big = 1.1 t_small = 0.65 shadow_tex = ba.gettexture('shadowSharp') for tex in [ 'powerupPunch', 'powerupShield', 'powerupBomb', 'powerupHealth', 'powerupIceBombs', 'powerupImpactBombs', 'powerupStickyBombs', 'powerupLandMines', 'powerupCurse' ]: name = ba.Lstr(resource=self._r + '.' + tex + 'NameText') desc = ba.Lstr(resource=self._r + '.' + tex + 'DescriptionText') v -= spacing * 60.0 ba.imagewidget( parent=self._subcontainer, size=(shadow_size, shadow_size), position=(h + mm1 + shadow_offs_x - 0.5 * shadow_size, v + shadow_offs_y - 0.5 * shadow_size), texture=shadow_tex, color=(0, 0, 0), opacity=0.5) ba.imagewidget(parent=self._subcontainer, size=(icon_size, icon_size), position=(h + mm1 - 0.5 * icon_size, v - 0.5 * icon_size), texture=ba.gettexture(tex)) txt_scale = t_big txtl = name ba.textwidget(parent=self._subcontainer, position=(h + mm2, v + 3), size=(0, 0), scale=txt_scale, maxwidth=200, flatness=1.0, text=txtl, h_align='left', color=header2, v_align='center') txt_scale = t_small txtl = desc ba.textwidget(parent=self._subcontainer, position=(h + mm3, v), size=(0, 0), scale=txt_scale, maxwidth=300, flatness=1.0, text=txtl, h_align='left', color=paragraph, v_align='center', res_scale=0.5)
def __init__(self, transition: Optional[str] = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-locals # pylint: disable=too-many-statements ba.set_analytics_screen('Bapman Window') 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 width = 900 if ba.app.small_ui else 580 x_inset = 75 if ba.app.small_ui else 0 height = 435 # button_height = 42 self._r = 'bapmanWindow' top_extra = 20 if ba.app.small_ui else 0 super().__init__(root_widget=ba.containerwidget( size=(width, height + top_extra), transition=transition, toolbar_visibility='menu_minimal', scale_origin_stack_offset=scale_origin, scale=( 1.75 if ba.app.small_ui else 1.35 if ba.app.med_ui else 1.0), stack_offset=(0, -8) if ba.app.small_ui else (0, 0))) if ba.app.toolbars and ba.app.small_ui: self._back_button = None ba.containerwidget(edit=self._root_widget, on_cancel_call=self._do_back) else: self._back_button = btn = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(40 + x_inset, height - 55), size=(130, 60), scale=0.8, text_scale=1.2, label=ba.Lstr(resource='backText'), button_type='back', on_activate_call=self._do_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'), # FIXME text='Bapman', color=ba.app.title_color, h_align='center', v_align='center', maxwidth=130) 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)) v = height - 80 v -= 145 basew = 280 if ba.app.small_ui else 230 baseh = 170 x_offs = x_inset + (105 if ba.app.small_ui else 72) - basew # now unused x_offs2 = x_offs + basew - 7 x_offs3 = x_offs + 2 * (basew - 7) x_offs4 = x_offs2 x_offs5 = x_offs3 def _b_title(x: float, y: float, button: ba.Widget, text: Union[str, ba.Lstr]) -> None: ba.textwidget(parent=self._root_widget, text=text, position=(x + basew * 0.47, y + baseh * 0.22), maxwidth=basew * 0.7, size=(0, 0), h_align='center', v_align='center', draw_controller=button, color=(0.7, 0.9, 0.7, 1.0)) ctb = self._browse_installed_button = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(x_offs2, v), size=(basew, baseh), button_type='square', label='', on_activate_call=self._do_browse_installed) if ba.app.toolbars and self._back_button is None: bbtn = _ba.get_special_widget('back_button') ba.widget(edit=ctb, left_widget=bbtn) _b_title( x_offs2, v, ctb, # ba.Lstr(resource=self._r + '.browseInstalledText') # FIXME "Installed") imgw = imgh = 130 ba.imagewidget(parent=self._root_widget, position=(x_offs2 + basew * 0.49 - imgw * 0.5, v + 35), size=(imgw, imgh), color=(0.4, 0.5, 1.0), texture=ba.gettexture('folder'), draw_controller=ctb) gfxb = self._search_button = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(x_offs3, v), size=(basew, baseh), button_type='square', label='', on_activate_call=self._do_search) if ba.app.toolbars: pbtn = _ba.get_special_widget('party_button') ba.widget(edit=gfxb, up_widget=pbtn, right_widget=pbtn) _b_title( x_offs3, v, gfxb, # ba.Lstr(resource=self._r + '.searchText') # FIXME 'Search new packages') imgw = imgh = 110 ba.imagewidget(parent=self._root_widget, position=(x_offs3 + basew * 0.49 - imgw * 0.5, v + 42), size=(imgw, imgh), texture=ba.gettexture('downButton'), draw_controller=gfxb) v -= (baseh - 5) abtn = self._install_local_button = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(x_offs4, v), size=(basew, baseh), button_type='square', label='', on_activate_call=self._do_install_local) _b_title( x_offs4, v, abtn, # ba.Lstr(resource=self._r + '.installLocalText') # FIXME 'Install local package') imgw = imgh = 120 ba.imagewidget(parent=self._root_widget, position=(x_offs4 + basew * 0.49 - imgw * 0.5 + 5, v + 35), size=(imgw, imgh), color=(1, 1, 1), texture=ba.gettexture('file'), draw_controller=abtn) avb = self._browse_repos_button = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(x_offs5, v), size=(basew, baseh), button_type='square', label='', on_activate_call=self._do_browse_repos) _b_title( x_offs5, v, avb, # ba.Lstr(resource=self._r + '.browseReposText') # FIXME 'Repositories') imgw = imgh = 120 ba.imagewidget(parent=self._root_widget, position=(x_offs5 + basew * 0.49 - imgw * 0.5 + 5, v + 35), size=(imgw, imgh), color=(0.8, 0.95, 1), texture=ba.gettexture('advancedIcon'), draw_controller=avb)
def __init__(self, transition: str = 'in_right', modal: bool = False, origin_widget: ba.Widget = None): from ba.internal import get_cached_league_rank_data from ba.deprecated import get_resource ba.set_analytics_screen('League Rank Window') self._league_rank_data: Optional[Dict[str, Any]] = None self._modal = modal # 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._width = 1320 if ba.app.small_ui else 1120 x_inset = 100 if ba.app.small_ui else 0 self._height = (657 if ba.app.small_ui else 710 if ba.app.med_ui else 800) self._r = 'coopSelectWindow' self._rdict = get_resource(self._r) top_extra = 20 if ba.app.small_ui else 0 self._league_url_arg = '' self._is_current_season = False self._can_do_more_button = True super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height + top_extra), stack_offset=(0, -15) if ba.app.small_ui else ( 0, 10) if ba.app.med_ui else (0, 0), transition=transition, scale_origin_stack_offset=scale_origin, scale=( 1.2 if ba.app.small_ui else 0.93 if ba.app.med_ui else 0.8))) self._back_button = btn = ba.buttonwidget( parent=self._root_widget, position=(75 + x_inset, self._height - 87 - (4 if ba.app.small_ui else 0)), size=(120, 60), scale=1.2, autoselect=True, label=ba.Lstr(resource='doneText' if self._modal else 'backText'), button_type=None if self._modal else 'back', on_activate_call=self._back) self._title_text = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height - 56), size=(0, 0), text=ba.Lstr( resource='league.leagueRankText', fallback_resource='coopSelectWindow.powerRankingText'), h_align="center", color=ba.app.title_color, scale=1.4, maxwidth=600, v_align="center") ba.buttonwidget(edit=btn, button_type='backSmall', position=(75 + x_inset, self._height - 87 - (2 if ba.app.small_ui else 0)), size=(60, 55), label=ba.charstr(ba.SpecialChar.BACK)) self._scroll_width = self._width - (130 + 2 * x_inset) self._scroll_height = self._height - 160 self._scrollwidget = ba.scrollwidget(parent=self._root_widget, highlight=False, position=(65 + x_inset, 70), size=(self._scroll_width, self._scroll_height), center_small_content=True) ba.widget(edit=self._scrollwidget, autoselect=True) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) ba.containerwidget(edit=self._root_widget, cancel_button=self._back_button, selected_child=self._back_button) self._last_power_ranking_query_time: Optional[float] = None self._doing_power_ranking_query = False self._subcontainer: Optional[ba.Widget] = None self._subcontainerwidth = 800 self._subcontainerheight = 483 self._power_ranking_score_widgets: List[ba.Widget] = [] self._season_popup_menu: Optional[popup_ui.PopupMenu] = None self._requested_season: Optional[str] = None self._season: Optional[str] = None # take note of our account state; we'll refresh later if this changes self._account_state = _ba.get_account_state() self._refresh() self._restore_state() # if we've got cached power-ranking data already, display it info = get_cached_league_rank_data() if info is not None: self._update_for_league_rank_data(info) self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True) self._update(show=(info is None))
def __init__(self, transition: Optional[str] = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-locals # pylint: disable=too-many-statements from bastd.ui.tabs import TabRow ba.set_analytics_screen('Watch Window') 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 ba.app.ui.set_main_menu_location('Watch') self._tab_data: Dict[str, Any] = {} self._my_replays_scroll_width: Optional[float] = None self._my_replays_watch_replay_button: Optional[ba.Widget] = None self._scrollwidget: Optional[ba.Widget] = None self._columnwidget: Optional[ba.Widget] = None self._my_replay_selected: Optional[str] = None self._my_replays_rename_window: Optional[ba.Widget] = None self._my_replay_rename_text: Optional[ba.Widget] = None self._r = 'watchWindow' uiscale = ba.app.ui.uiscale self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = (578 if uiscale is ba.UIScale.SMALL else 670 if uiscale is ba.UIScale.MEDIUM else 800) self._current_tab: Optional[WatchWindow.TabID] = None extra_top = 20 if uiscale is ba.UIScale.SMALL else 0 super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height + extra_top), transition=transition, toolbar_visibility='menu_minimal', scale_origin_stack_offset=scale_origin, scale=(1.3 if uiscale is ba.UIScale.SMALL else 0.97 if uiscale is ba.UIScale.MEDIUM else 0.8), stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else ( 0, 15) if uiscale is ba.UIScale.MEDIUM else (0, 0))) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: ba.containerwidget(edit=self._root_widget, on_cancel_call=self._back) self._back_button = None else: self._back_button = btn = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(70 + x_inset, self._height - 74), size=(140, 60), scale=1.1, label=ba.Lstr(resource='backText'), button_type='back', on_activate_call=self._back) ba.containerwidget(edit=self._root_widget, cancel_button=btn) ba.buttonwidget(edit=btn, 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 - 38), size=(0, 0), color=ba.app.ui.title_color, scale=1.5, h_align='center', v_align='center', text=ba.Lstr(resource=self._r + '.titleText'), maxwidth=400) tabdefs = [ (self.TabID.MY_REPLAYS, ba.Lstr(resource=self._r + '.myReplaysText')), # (self.TabID.TEST_TAB, ba.Lstr(value='Testing')), ] scroll_buffer_h = 130 + 2 * x_inset tab_buffer_h = 750 + 2 * x_inset self._tab_row = TabRow(self._root_widget, tabdefs, pos=(tab_buffer_h * 0.5, self._height - 130), size=(self._width - tab_buffer_h, 50), on_select_call=self._set_tab) if ba.app.ui.use_toolbars: first_tab = self._tab_row.tabs[tabdefs[0][0]] last_tab = self._tab_row.tabs[tabdefs[-1][0]] ba.widget(edit=last_tab.button, right_widget=_ba.get_special_widget('party_button')) if uiscale is ba.UIScale.SMALL: bbtn = _ba.get_special_widget('back_button') ba.widget(edit=first_tab.button, up_widget=bbtn, left_widget=bbtn) self._scroll_width = self._width - scroll_buffer_h self._scroll_height = self._height - 180 # Not actually using a scroll widget anymore; just an image. scroll_left = (self._width - self._scroll_width) * 0.5 scroll_bottom = self._height - self._scroll_height - 79 - 48 buffer_h = 10 buffer_v = 4 ba.imagewidget(parent=self._root_widget, position=(scroll_left - buffer_h, scroll_bottom - buffer_v), size=(self._scroll_width + 2 * buffer_h, self._scroll_height + 2 * buffer_v), texture=ba.gettexture('scrollWidget'), model_transparent=ba.getmodel('softEdgeOutside')) self._tab_container: Optional[ba.Widget] = None self._restore_state()
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.ui.use_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.ui.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.ui.use_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 __init__(self, transition: Optional[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.gather.abouttab import AboutGatherTab from bastd.ui.gather.manualtab import ManualGatherTab from bastd.ui.gather.privatetab import PrivateGatherTab from bastd.ui.gather.publictab import PublicGatherTab from bastd.ui.gather.nearbytab import NearbyGatherTab ba.set_analytics_screen('Gather Window') 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 ba.app.ui.set_main_menu_location('Gather') _ba.set_party_icon_always_visible(True) uiscale = ba.app.ui.uiscale self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 x_offs = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = (582 if uiscale is ba.UIScale.SMALL else 680 if uiscale is ba.UIScale.MEDIUM else 800) self._current_tab: Optional[GatherWindow.TabID] = None extra_top = 20 if uiscale is ba.UIScale.SMALL else 0 self._r = 'gatherWindow' super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height + extra_top), transition=transition, toolbar_visibility='menu_minimal', scale_origin_stack_offset=scale_origin, scale=(1.3 if uiscale is ba.UIScale.SMALL else 0.97 if uiscale is ba.UIScale.MEDIUM else 0.8), stack_offset=(0, -11) if uiscale is ba.UIScale.SMALL else ( 0, 0) if uiscale is ba.UIScale.MEDIUM else (0, 0))) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: ba.containerwidget(edit=self._root_widget, on_cancel_call=self._back) self._back_button = None else: self._back_button = btn = ba.buttonwidget( parent=self._root_widget, position=(70 + x_offs, self._height - 74), size=(140, 60), scale=1.1, 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.buttonwidget(edit=btn, button_type='backSmall', position=(70 + x_offs, self._height - 78), size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) condensed = uiscale is not ba.UIScale.LARGE t_offs_y = (0 if not condensed else 25 if uiscale is ba.UIScale.MEDIUM else 17) ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height - 42 + t_offs_y), size=(0, 0), color=ba.app.ui.title_color, scale=(1.5 if not condensed else 1.0 if uiscale is ba.UIScale.MEDIUM else 0.6), h_align='center', v_align='center', text=ba.Lstr(resource=self._r + '.titleText'), maxwidth=550) scroll_buffer_h = 130 + 2 * x_offs tab_buffer_h = ((320 if condensed else 250) + 2 * x_offs) # Build up the set of tabs we want. tabdefs: list[tuple[GatherWindow.TabID, ba.Lstr]] = [ (self.TabID.ABOUT, ba.Lstr(resource=self._r + '.aboutText')) ] if _ba.get_account_misc_read_val('enablePublicParties', True): tabdefs.append((self.TabID.INTERNET, ba.Lstr(resource=self._r + '.publicText'))) tabdefs.append( (self.TabID.PRIVATE, ba.Lstr(resource=self._r + '.privateText'))) tabdefs.append( (self.TabID.NEARBY, ba.Lstr(resource=self._r + '.nearbyText'))) tabdefs.append( (self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText'))) # On small UI, push our tabs up closer to the top of the screen to # save a bit of space. tabs_top_extra = 42 if condensed else 0 self._tab_row = TabRow(self._root_widget, tabdefs, pos=(tab_buffer_h * 0.5, self._height - 130 + tabs_top_extra), size=(self._width - tab_buffer_h, 50), on_select_call=ba.WeakCall(self._set_tab)) # Now instantiate handlers for these tabs. tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { self.TabID.ABOUT: AboutGatherTab, self.TabID.MANUAL: ManualGatherTab, self.TabID.PRIVATE: PrivateGatherTab, self.TabID.INTERNET: PublicGatherTab, self.TabID.NEARBY: NearbyGatherTab } self._tabs: dict[GatherWindow.TabID, GatherTab] = {} for tab_id in self._tab_row.tabs: tabtype = tabtypes.get(tab_id) if tabtype is not None: self._tabs[tab_id] = tabtype(self) if ba.app.ui.use_toolbars: ba.widget(edit=self._tab_row.tabs[tabdefs[-1][0]].button, right_widget=_ba.get_special_widget('party_button')) if uiscale is ba.UIScale.SMALL: ba.widget(edit=self._tab_row.tabs[tabdefs[0][0]].button, left_widget=_ba.get_special_widget('back_button')) self._scroll_width = self._width - scroll_buffer_h self._scroll_height = self._height - 180.0 + tabs_top_extra self._scroll_left = (self._width - self._scroll_width) * 0.5 self._scroll_bottom = (self._height - self._scroll_height - 79 - 48 + tabs_top_extra) buffer_h = 10 buffer_v = 4 # Not actually using a scroll widget anymore; just an image. ba.imagewidget(parent=self._root_widget, position=(self._scroll_left - buffer_h, self._scroll_bottom - buffer_v), size=(self._scroll_width + 2 * buffer_h, self._scroll_height + 2 * buffer_v), texture=ba.gettexture('scrollWidget'), model_transparent=ba.getmodel('softEdgeOutside')) self._tab_container: Optional[ba.Widget] = None self._restore_state()
def on_begin(self) -> None: # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-statements from bastd.actor.text import Text from bastd.actor.image import Image from ba.deprecated import get_resource ba.set_analytics_screen('FreeForAll Series Victory Screen' if self. _is_ffa else 'Teams Series Victory Screen') if ba.app.uiscale is ba.UIScale.LARGE: sval = ba.Lstr(resource='pressAnyKeyButtonPlayAgainText') else: sval = ba.Lstr(resource='pressAnyButtonPlayAgainText') self._show_up_next = False self._custom_continue_message = sval super().on_begin() winning_sessionteam = self.settings_raw['winner'] # Pause a moment before playing victory music. ba.timer(0.6, ba.WeakCall(self._play_victory_music)) ba.timer(4.4, ba.WeakCall(self._show_winner, self.settings_raw['winner'])) ba.timer(4.6, ba.Call(ba.playsound, self._score_display_sound)) # Score / Name / Player-record. player_entries: List[Tuple[int, str, ba.PlayerRecord]] = [] # Note: for ffa, exclude players who haven't entered the game yet. if self._is_ffa: for _pkey, prec in self.stats.get_records().items(): if prec.player.in_game: player_entries.append( (prec.player.sessionteam.customdata['score'], prec.getname(full=True), prec)) player_entries.sort(reverse=True, key=lambda x: x[0]) else: for _pkey, prec in self.stats.get_records().items(): player_entries.append((prec.score, prec.name_full, prec)) player_entries.sort(reverse=True, key=lambda x: x[0]) ts_height = 300.0 ts_h_offs = -390.0 tval = 6.4 t_incr = 0.12 always_use_first_to = get_resource('bestOfUseFirstToInstead') session = self.session if self._is_ffa: assert isinstance(session, ba.FreeForAllSession) txt = ba.Lstr( value='${A}:', subs=[('${A}', ba.Lstr(resource='firstToFinalText', subs=[('${COUNT}', str(session.get_ffa_series_length()))])) ]) else: assert isinstance(session, ba.MultiTeamSession) # Some languages may prefer to always show 'first to X' instead of # 'best of X'. # FIXME: This will affect all clients connected to us even if # they're not using this language. Should try to come up # with a wording that works everywhere. if always_use_first_to: txt = ba.Lstr( value='${A}:', subs=[ ('${A}', ba.Lstr(resource='firstToFinalText', subs=[ ('${COUNT}', str(session.get_series_length() / 2 + 1)) ])) ]) else: txt = ba.Lstr( value='${A}:', subs=[('${A}', ba.Lstr(resource='bestOfFinalText', subs=[('${COUNT}', str(session.get_series_length()))])) ]) Text(txt, v_align=Text.VAlign.CENTER, maxwidth=300, color=(0.5, 0.5, 0.5, 1.0), position=(0, 220), scale=1.2, transition=Text.Transition.IN_TOP_SLOW, h_align=Text.HAlign.CENTER, transition_delay=t_incr * 4).autoretain() win_score = (session.get_series_length() - 1) // 2 + 1 lose_score = 0 for team in self.teams: if team.sessionteam.customdata['score'] != win_score: lose_score = team.sessionteam.customdata['score'] if not self._is_ffa: Text(ba.Lstr(resource='gamesToText', subs=[('${WINCOUNT}', str(win_score)), ('${LOSECOUNT}', str(lose_score))]), color=(0.5, 0.5, 0.5, 1.0), maxwidth=160, v_align=Text.VAlign.CENTER, position=(0, -215), scale=1.8, transition=Text.Transition.IN_LEFT, h_align=Text.HAlign.CENTER, transition_delay=4.8 + t_incr * 4).autoretain() if self._is_ffa: v_extra = 120 else: v_extra = 0 mvp: Optional[ba.PlayerRecord] = None mvp_name: Optional[str] = None # Show game MVP. if not self._is_ffa: mvp, mvp_name = None, None for entry in player_entries: if entry[2].team == winning_sessionteam: mvp = entry[2] mvp_name = entry[1] break if mvp is not None: Text(ba.Lstr(resource='mostValuablePlayerText'), color=(0.5, 0.5, 0.5, 1.0), v_align=Text.VAlign.CENTER, maxwidth=300, position=(180, ts_height / 2 + 15), transition=Text.Transition.IN_LEFT, h_align=Text.HAlign.LEFT, transition_delay=tval).autoretain() tval += 4 * t_incr Image(mvp.get_icon(), position=(230, ts_height / 2 - 55 + 14 - 5), scale=(70, 70), transition=Image.Transition.IN_LEFT, transition_delay=tval).autoretain() assert mvp_name is not None Text(ba.Lstr(value=mvp_name), position=(280, ts_height / 2 - 55 + 15 - 5), h_align=Text.HAlign.LEFT, v_align=Text.VAlign.CENTER, maxwidth=170, scale=1.3, color=ba.safecolor(mvp.team.color + (1, )), transition=Text.Transition.IN_LEFT, transition_delay=tval).autoretain() tval += 4 * t_incr # Most violent. most_kills = 0 for entry in player_entries: if entry[2].kill_count >= most_kills: mvp = entry[2] mvp_name = entry[1] most_kills = entry[2].kill_count if mvp is not None: Text(ba.Lstr(resource='mostViolentPlayerText'), color=(0.5, 0.5, 0.5, 1.0), v_align=Text.VAlign.CENTER, maxwidth=300, position=(180, ts_height / 2 - 150 + v_extra + 15), transition=Text.Transition.IN_LEFT, h_align=Text.HAlign.LEFT, transition_delay=tval).autoretain() Text(ba.Lstr(value='(${A})', subs=[('${A}', ba.Lstr(resource='killsTallyText', subs=[('${COUNT}', str(most_kills))])) ]), position=(260, ts_height / 2 - 150 - 15 + v_extra), color=(0.3, 0.3, 0.3, 1.0), scale=0.6, h_align=Text.HAlign.LEFT, transition=Text.Transition.IN_LEFT, transition_delay=tval).autoretain() tval += 4 * t_incr Image(mvp.get_icon(), position=(233, ts_height / 2 - 150 - 30 - 46 + 25 + v_extra), scale=(50, 50), transition=Image.Transition.IN_LEFT, transition_delay=tval).autoretain() assert mvp_name is not None Text(ba.Lstr(value=mvp_name), position=(270, ts_height / 2 - 150 - 30 - 36 + v_extra + 15), h_align=Text.HAlign.LEFT, v_align=Text.VAlign.CENTER, maxwidth=180, color=ba.safecolor(mvp.team.color + (1, )), transition=Text.Transition.IN_LEFT, transition_delay=tval).autoretain() tval += 4 * t_incr # Most killed. most_killed = 0 mkp, mkp_name = None, None for entry in player_entries: if entry[2].killed_count >= most_killed: mkp = entry[2] mkp_name = entry[1] most_killed = entry[2].killed_count if mkp is not None: Text(ba.Lstr(resource='mostViolatedPlayerText'), color=(0.5, 0.5, 0.5, 1.0), v_align=Text.VAlign.CENTER, maxwidth=300, position=(180, ts_height / 2 - 300 + v_extra + 15), transition=Text.Transition.IN_LEFT, h_align=Text.HAlign.LEFT, transition_delay=tval).autoretain() Text(ba.Lstr(value='(${A})', subs=[('${A}', ba.Lstr(resource='deathsTallyText', subs=[('${COUNT}', str(most_killed))])) ]), position=(260, ts_height / 2 - 300 - 15 + v_extra), h_align=Text.HAlign.LEFT, scale=0.6, color=(0.3, 0.3, 0.3, 1.0), transition=Text.Transition.IN_LEFT, transition_delay=tval).autoretain() tval += 4 * t_incr Image(mkp.get_icon(), position=(233, ts_height / 2 - 300 - 30 - 46 + 25 + v_extra), scale=(50, 50), transition=Image.Transition.IN_LEFT, transition_delay=tval).autoretain() assert mkp_name is not None Text(ba.Lstr(value=mkp_name), position=(270, ts_height / 2 - 300 - 30 - 36 + v_extra + 15), h_align=Text.HAlign.LEFT, v_align=Text.VAlign.CENTER, color=ba.safecolor(mkp.team.color + (1, )), maxwidth=180, transition=Text.Transition.IN_LEFT, transition_delay=tval).autoretain() tval += 4 * t_incr # Now show individual scores. tdelay = tval Text(ba.Lstr(resource='finalScoresText'), color=(0.5, 0.5, 0.5, 1.0), position=(ts_h_offs, ts_height / 2), transition=Text.Transition.IN_RIGHT, transition_delay=tdelay).autoretain() tdelay += 4 * t_incr v_offs = 0.0 tdelay += len(player_entries) * 8 * t_incr for _score, name, prec in player_entries: tdelay -= 4 * t_incr v_offs -= 40 Text(str(prec.team.customdata['score']) if self._is_ffa else str(prec.score), color=(0.5, 0.5, 0.5, 1.0), position=(ts_h_offs + 230, ts_height / 2 + v_offs), h_align=Text.HAlign.RIGHT, transition=Text.Transition.IN_RIGHT, transition_delay=tdelay).autoretain() tdelay -= 4 * t_incr Image(prec.get_icon(), position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15), scale=(30, 30), transition=Image.Transition.IN_LEFT, transition_delay=tdelay).autoretain() Text(ba.Lstr(value=name), position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15), h_align=Text.HAlign.LEFT, v_align=Text.VAlign.CENTER, maxwidth=180, color=ba.safecolor(prec.team.color + (1, )), transition=Text.Transition.IN_RIGHT, transition_delay=tdelay).autoretain() ba.timer(15.0, ba.WeakCall(self._show_tips))
def __init__(self, data: Dict[str, Any]): from ba.internal import is_browser_likely_available ba.set_analytics_screen('Friend Promo Code') self._width = 650 self._height = 400 uiscale = ba.app.uiscale super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height), color=(0.45, 0.63, 0.15), transition='in_scale', scale=(1.7 if uiscale is ba.UIScale.SMALL else 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0))) self._data = copy.deepcopy(data) ba.playsound(ba.getsound('cashRegister')) ba.playsound(ba.getsound('swish')) self._cancel_button = ba.buttonwidget(parent=self._root_widget, scale=0.7, position=(50, self._height - 50), size=(60, 60), 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) ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height * 0.8), size=(0, 0), color=ba.app.ui.infotextcolor, scale=1.0, flatness=1.0, h_align='center', v_align='center', text=ba.Lstr(resource='gatherWindow.shareThisCodeWithFriendsText'), maxwidth=self._width * 0.85) ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height * 0.645), size=(0, 0), color=(1.0, 3.0, 1.0), scale=2.0, h_align='center', v_align='center', text=data['code'], maxwidth=self._width * 0.85) award_str: Optional[Union[str, ba.Lstr]] if self._data['awardTickets'] != 0: award_str = ba.Lstr( resource='gatherWindow.friendPromoCodeAwardText', subs=[('${COUNT}', str(self._data['awardTickets']))]) else: award_str = '' ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height * 0.37), size=(0, 0), color=ba.app.ui.infotextcolor, scale=1.0, flatness=1.0, h_align='center', v_align='center', text=ba.Lstr( value='${A}\n${B}\n${C}\n${D}', subs=[ ('${A}', ba.Lstr( resource='gatherWindow.friendPromoCodeRedeemLongText', subs=[('${COUNT}', str(self._data['tickets'])), ('${MAX_USES}', str(self._data['usesRemaining']))])), ('${B}', ba.Lstr(resource=( 'gatherWindow.friendPromoCodeWhereToEnterText'))), ('${C}', award_str), ('${D}', ba.Lstr(resource='gatherWindow.friendPromoCodeExpireText', subs=[('${EXPIRE_HOURS}', str(self._data['expireHours']))])) ]), maxwidth=self._width * 0.9, max_height=self._height * 0.35) if is_browser_likely_available(): xoffs = 0 ba.buttonwidget(parent=self._root_widget, size=(200, 40), position=(self._width * 0.5 - 100 + xoffs, 39), autoselect=True, label=ba.Lstr(resource='gatherWindow.emailItText'), on_activate_call=ba.WeakCall(self._email))
def __init__(self, transition: str = 'in_right', from_modal_store: bool = False, modal: bool = False, origin_widget: ba.Widget = None, store_back_location: str = None): # pylint: disable=too-many-statements # pylint: disable=too-many-locals ba.set_analytics_screen('Get Tickets Window') self._transitioning_out = False self._store_back_location = store_back_location # ew. self._ad_button_greyed = False self._smooth_update_timer: Optional[ba.Timer] = None self._ad_button = None self._ad_label = None self._ad_image = None self._ad_time_text = None # 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.uiscale self._width = 1000.0 if uiscale is ba.UIScale.SMALL else 800.0 x_inset = 100.0 if uiscale is ba.UIScale.SMALL else 0.0 self._height = 480.0 self._modal = modal self._from_modal_store = from_modal_store self._r = 'getTicketsWindow' 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, color=(0.4, 0.37, 0.55), scale=(1.63 if uiscale is ba.UIScale.SMALL else 1.2 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -3) if uiscale is ba.UIScale.SMALL else (0, 0))) btn = ba.buttonwidget( parent=self._root_widget, position=(55 + x_inset, self._height - 79), size=(140, 60), scale=1.0, autoselect=True, label=ba.Lstr(resource='doneText' if modal else 'backText'), button_type='regular' if modal else 'back', 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 - 55), size=(0, 0), color=ba.app.title_color, scale=1.2, h_align='center', v_align='center', text=ba.Lstr(resource=self._r + '.titleText'), maxwidth=290) if not modal: ba.buttonwidget(edit=btn, button_type='backSmall', size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) b_size = (220.0, 180.0) v = self._height - b_size[1] - 80 spacing = 1 self._ad_button = None def _add_button(item: str, position: Tuple[float, float], size: Tuple[float, float], label: ba.Lstr, price: str = None, tex_name: str = None, tex_opacity: float = 1.0, tex_scale: float = 1.0, enabled: bool = True, text_scale: float = 1.0) -> ba.Widget: btn2 = ba.buttonwidget( parent=self._root_widget, position=position, button_type='square', size=size, label='', autoselect=True, color=None if enabled else (0.5, 0.5, 0.5), on_activate_call=(ba.Call(self._purchase, item) if enabled else self._disabled_press)) txt = ba.textwidget(parent=self._root_widget, text=label, position=(position[0] + size[0] * 0.5, position[1] + size[1] * 0.3), scale=text_scale, maxwidth=size[0] * 0.75, size=(0, 0), h_align='center', v_align='center', draw_controller=btn2, color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2)) if price is not None and enabled: ba.textwidget(parent=self._root_widget, text=price, position=(position[0] + size[0] * 0.5, position[1] + size[1] * 0.17), scale=0.7, maxwidth=size[0] * 0.75, size=(0, 0), h_align='center', v_align='center', draw_controller=btn2, color=(0.4, 0.9, 0.4, 1.0)) i = None if tex_name is not None: tex_size = 90.0 * tex_scale i = ba.imagewidget( parent=self._root_widget, texture=ba.gettexture(tex_name), position=(position[0] + size[0] * 0.5 - tex_size * 0.5, position[1] + size[1] * 0.66 - tex_size * 0.5), size=(tex_size, tex_size), draw_controller=btn2, opacity=tex_opacity * (1.0 if enabled else 0.25)) if item == 'ad': self._ad_button = btn2 self._ad_label = txt assert i is not None self._ad_image = i self._ad_time_text = ba.textwidget( parent=self._root_widget, text='1m 10s', position=(position[0] + size[0] * 0.5, position[1] + size[1] * 0.5), scale=text_scale * 1.2, maxwidth=size[0] * 0.85, size=(0, 0), h_align='center', v_align='center', draw_controller=btn2, color=(0.4, 0.9, 0.4, 1.0)) return btn2 rsrc = self._r + '.ticketsText' c2txt = ba.Lstr( resource=rsrc, subs=[('${COUNT}', str(_ba.get_account_misc_read_val('tickets2Amount', 500)))]) c3txt = ba.Lstr( resource=rsrc, subs=[('${COUNT}', str(_ba.get_account_misc_read_val('tickets3Amount', 1500)))]) c4txt = ba.Lstr( resource=rsrc, subs=[('${COUNT}', str(_ba.get_account_misc_read_val('tickets4Amount', 5000)))]) c5txt = ba.Lstr( resource=rsrc, subs=[('${COUNT}', str(_ba.get_account_misc_read_val('tickets5Amount', 15000)))]) h = 110.0 # enable buttons if we have prices.. tickets2_price = _ba.get_price('tickets2') tickets3_price = _ba.get_price('tickets3') tickets4_price = _ba.get_price('tickets4') tickets5_price = _ba.get_price('tickets5') # TEMP # tickets1_price = '$0.99' # tickets2_price = '$4.99' # tickets3_price = '$9.99' # tickets4_price = '$19.99' # tickets5_price = '$49.99' _add_button('tickets2', enabled=(tickets2_price is not None), position=(self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h, v), size=b_size, label=c2txt, price=tickets2_price, tex_name='ticketsMore') # 0.99-ish _add_button('tickets3', enabled=(tickets3_price is not None), position=(self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h, v), size=b_size, label=c3txt, price=tickets3_price, tex_name='ticketRoll') # 4.99-ish v -= b_size[1] - 5 _add_button('tickets4', enabled=(tickets4_price is not None), position=(self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h, v), size=b_size, label=c4txt, price=tickets4_price, tex_name='ticketRollBig', tex_scale=1.2) # 9.99-ish _add_button('tickets5', enabled=(tickets5_price is not None), position=(self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h, v), size=b_size, label=c5txt, price=tickets5_price, tex_name='ticketRolls', tex_scale=1.2) # 19.99-ish self._enable_ad_button = _ba.has_video_ads() h = self._width * 0.5 + 110.0 v = self._height - b_size[1] - 115.0 if self._enable_ad_button: h_offs = 35 b_size_3 = (150, 120) cdb = _add_button( 'ad', position=(h + h_offs, v), size=b_size_3, label=ba.Lstr(resource=self._r + '.ticketsFromASponsorText', subs=[('${COUNT}', str( _ba.get_account_misc_read_val( 'sponsorTickets', 5)))]), tex_name='ticketsMore', enabled=self._enable_ad_button, tex_opacity=0.6, tex_scale=0.7, text_scale=0.7) ba.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7) if self._enable_ad_button else (0.5, 0.5, 0.5)) self._ad_free_text = ba.textwidget( parent=self._root_widget, text=ba.Lstr(resource=self._r + '.freeText'), position=(h + h_offs + b_size_3[0] * 0.5, v + b_size_3[1] * 0.5 + 25), size=(0, 0), color=(1, 1, 0, 1.0) if self._enable_ad_button else (1, 1, 1, 0.2), draw_controller=cdb, rotate=15, shadow=1.0, maxwidth=150, h_align='center', v_align='center', scale=1.0) v -= 125 else: v -= 20 if True: # pylint: disable=using-constant-test h_offs = 35 b_size_3 = (150, 120) cdb = _add_button( 'app_invite', position=(h + h_offs, v), size=b_size_3, label=ba.Lstr( resource='gatherWindow.earnTicketsForRecommendingText', subs=[ ('${COUNT}', str(_ba.get_account_misc_read_val( 'sponsorTickets', 5))) ]), tex_name='ticketsMore', enabled=True, tex_opacity=0.6, tex_scale=0.7, text_scale=0.7) ba.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7)) ba.textwidget(parent=self._root_widget, text=ba.Lstr(resource=self._r + '.freeText'), position=(h + h_offs + b_size_3[0] * 0.5, v + b_size_3[1] * 0.5 + 25), size=(0, 0), color=(1, 1, 0, 1.0), draw_controller=cdb, rotate=15, shadow=1.0, maxwidth=150, h_align='center', v_align='center', scale=1.0) tc_y_offs = 0 h = self._width - (185 + x_inset) v = self._height - 95 + tc_y_offs txt1 = (ba.Lstr( resource=self._r + '.youHaveText').evaluate().split('${COUNT}')[0].strip()) txt2 = (ba.Lstr( resource=self._r + '.youHaveText').evaluate().split('${COUNT}')[-1].strip()) ba.textwidget(parent=self._root_widget, text=txt1, position=(h, v), size=(0, 0), color=(0.5, 0.5, 0.6), maxwidth=200, h_align='center', v_align='center', scale=0.8) v -= 30 self._ticket_count_text = ba.textwidget(parent=self._root_widget, position=(h, v), size=(0, 0), color=(0.2, 1.0, 0.2), maxwidth=200, h_align='center', v_align='center', scale=1.6) v -= 30 ba.textwidget(parent=self._root_widget, text=txt2, position=(h, v), size=(0, 0), color=(0.5, 0.5, 0.6), maxwidth=200, h_align='center', v_align='center', scale=0.8) # update count now and once per second going forward.. self._ticking_node: Optional[ba.Node] = None self._smooth_ticket_count: Optional[float] = None self._ticket_count = 0 self._update() self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True) self._smooth_increase_speed = 1.0
def __init__(self, sessiontype: Type[ba.Session], transition: Optional[str] = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # pylint: disable=cyclic-import from bastd.ui.playlist import PlaylistTypeVars # 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 # Store state for when we exit the next game. if issubclass(sessiontype, ba.DualTeamSession): ba.app.main_window = 'Team Game Select' ba.set_analytics_screen('Teams Window') elif issubclass(sessiontype, ba.FreeForAllSession): ba.app.main_window = 'Free-for-All Game Select' ba.set_analytics_screen('FreeForAll Window') else: raise TypeError(f'Invalid sessiontype: {sessiontype}.') self._pvars = PlaylistTypeVars(sessiontype) self._sessiontype = sessiontype self._customize_button: Optional[ba.Widget] = None self._sub_width: Optional[float] = None self._sub_height: Optional[float] = None # On new installations, go ahead and create a few playlists # besides the hard-coded default one: if not _ba.get_account_misc_val('madeStandardPlaylists', False): _ba.add_transaction({ 'type': 'ADD_PLAYLIST', 'playlistType': 'Free-for-All', 'playlistName': ba.Lstr( resource='singleGamePlaylistNameText').evaluate().replace( '${GAME}', ba.Lstr(translate=('gameNames', 'Death Match')).evaluate()), 'playlist': [ { 'type': 'bs_death_match.DeathMatchGame', 'settings': { 'Epic Mode': False, 'Kills to Win Per Player': 10, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Doom Shroom' } }, { 'type': 'bs_death_match.DeathMatchGame', 'settings': { 'Epic Mode': False, 'Kills to Win Per Player': 10, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Crag Castle' } }, ] }) _ba.add_transaction({ 'type': 'ADD_PLAYLIST', 'playlistType': 'Team Tournament', 'playlistName': ba.Lstr( resource='singleGamePlaylistNameText').evaluate().replace( '${GAME}', ba.Lstr(translate=('gameNames', 'Capture the Flag')).evaluate()), 'playlist': [ { 'type': 'bs_capture_the_flag.CTFGame', 'settings': { 'map': 'Bridgit', 'Score to Win': 3, 'Flag Idle Return Time': 30, 'Flag Touch Return Time': 0, 'Respawn Times': 1.0, 'Time Limit': 600, 'Epic Mode': False } }, { 'type': 'bs_capture_the_flag.CTFGame', 'settings': { 'map': 'Roundabout', 'Score to Win': 2, 'Flag Idle Return Time': 30, 'Flag Touch Return Time': 0, 'Respawn Times': 1.0, 'Time Limit': 600, 'Epic Mode': False } }, { 'type': 'bs_capture_the_flag.CTFGame', 'settings': { 'map': 'Tip Top', 'Score to Win': 2, 'Flag Idle Return Time': 30, 'Flag Touch Return Time': 3, 'Respawn Times': 1.0, 'Time Limit': 300, 'Epic Mode': False } }, ] }) _ba.add_transaction({ 'type': 'ADD_PLAYLIST', 'playlistType': 'Team Tournament', 'playlistName': ba.Lstr(translate=('playlistNames', 'Just Sports')).evaluate(), 'playlist': [ { 'type': 'bs_hockey.HockeyGame', 'settings': { 'Time Limit': 0, 'map': 'Hockey Stadium', 'Score to Win': 1, 'Respawn Times': 1.0 } }, { 'type': 'bs_football.FootballTeamGame', 'settings': { 'Time Limit': 0, 'map': 'Football Stadium', 'Score to Win': 21, 'Respawn Times': 1.0 } }, ] }) _ba.add_transaction({ 'type': 'ADD_PLAYLIST', 'playlistType': 'Free-for-All', 'playlistName': ba.Lstr(translate=('playlistNames', 'Just Epic')).evaluate(), 'playlist': [{ 'type': 'bs_elimination.EliminationGame', 'settings': { 'Time Limit': 120, 'map': 'Tip Top', 'Respawn Times': 1.0, 'Lives Per Player': 1, 'Epic Mode': 1 } }] }) _ba.add_transaction({ 'type': 'SET_MISC_VAL', 'name': 'madeStandardPlaylists', 'value': True }) _ba.run_transactions() # Get the current selection (if any). self._selected_playlist = ba.app.config.get(self._pvars.config_name + ' Playlist Selection') uiscale = ba.app.uiscale self._width = 900 if uiscale is ba.UIScale.SMALL else 800 x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 self._height = (480 if uiscale is ba.UIScale.SMALL else 510 if uiscale is ba.UIScale.MEDIUM else 580) 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, toolbar_visibility='menu_full', scale_origin_stack_offset=scale_origin, scale=(1.69 if uiscale is ba.UIScale.SMALL else 1.05 if uiscale is ba.UIScale.MEDIUM else 0.9), stack_offset=(0, -26) if uiscale is ba.UIScale.SMALL else (0, 0))) self._back_button: Optional[ba.Widget] = ba.buttonwidget( parent=self._root_widget, position=(59 + x_inset, self._height - 70), size=(120, 60), scale=1.0, on_activate_call=self._on_back_press, autoselect=True, label=ba.Lstr(resource='backText'), button_type='back') ba.containerwidget(edit=self._root_widget, cancel_button=self._back_button) txt = self._title_text = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height - 41), size=(0, 0), text=self._pvars.window_title_name, scale=1.3, res_scale=1.5, color=ba.app.heading_color, h_align='center', v_align='center') if uiscale is ba.UIScale.SMALL and ba.app.toolbars: ba.textwidget(edit=txt, text='') ba.buttonwidget(edit=self._back_button, button_type='backSmall', size=(60, 54), position=(59 + x_inset, self._height - 67), label=ba.charstr(ba.SpecialChar.BACK)) if uiscale is ba.UIScale.SMALL and ba.app.toolbars: self._back_button.delete() self._back_button = None ba.containerwidget(edit=self._root_widget, on_cancel_call=self._on_back_press) scroll_offs = 33 else: scroll_offs = 0 self._scroll_width = self._width - (100 + 2 * x_inset) self._scroll_height = self._height - ( 146 if uiscale is ba.UIScale.SMALL and ba.app.toolbars else 136) self._scrollwidget = ba.scrollwidget( parent=self._root_widget, highlight=False, size=(self._scroll_width, self._scroll_height), position=((self._width - self._scroll_width) * 0.5, 65 + scroll_offs)) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) self._subcontainer: Optional[ba.Widget] = None self._config_name_full = self._pvars.config_name + ' Playlists' self._last_config = None # Update now and once per second. # (this should do our initial refresh) self._update() self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True)
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._sign_in_game_circle_button: Optional[ba.Widget] = None self._sign_in_v2_button: Optional[ba.Widget] = None self._sign_in_device_button: Optional[ba.Widget] = None 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_v1_account_state() == 'signed_in') self._account_state_num = _ba.get_v1_account_state_num() self._show_linked = (self._signed_in and _ba.get_v1_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_v1_account_type() else: account_type = None self._can_reset_achievements = (account_type == 'Game Center') app = ba.app uiscale = app.ui.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') # Ditto with shiny new V2 ones. if bool(True): self._show_sign_in_buttons.append('V2') 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), claims_left_right=True, claims_tab=True, selection_loops_to_parent=True) self._subcontainer: Optional[ba.Widget] = None self._refresh() self._restore_state()
def __init__(self) -> None: ba.set_analytics_screen('AppInviteWindow') self._data: Optional[Dict[str, Any]] = None self._width = 650 self._height = 400 uiscale = ba.app.uiscale super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height), transition='in_scale', scale=(1.8 if uiscale is ba.UIScale.SMALL else 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0))) self._cancel_button = ba.buttonwidget(parent=self._root_widget, scale=0.8, position=(60, self._height - 50), size=(50, 50), label='', on_activate_call=self.close, autoselect=True, color=(0.4, 0.4, 0.6), icon=ba.gettexture('crossOut'), iconscale=1.2) ba.containerwidget(edit=self._root_widget, cancel_button=self._cancel_button) ba.textwidget( parent=self._root_widget, size=(0, 0), position=(self._width * 0.5, self._height * 0.5 + 110), autoselect=True, scale=0.8, maxwidth=self._width * 0.9, h_align='center', v_align='center', color=(0.3, 0.8, 0.3), flatness=1.0, text=ba.Lstr( resource='gatherWindow.earnTicketsForRecommendingAmountText', fallback_resource=( 'gatherWindow.earnTicketsForRecommendingText'), subs=[ ('${COUNT}', str(_ba.get_account_misc_read_val('friendTryTickets', 300))), ('${YOU_COUNT}', str( _ba.get_account_misc_read_val('friendTryAwardTickets', 100))) ])) or_text = ba.Lstr(resource='orText', subs=[('${A}', ''), ('${B}', '')]).evaluate().strip() ba.buttonwidget( parent=self._root_widget, size=(250, 150), position=(self._width * 0.5 - 125, self._height * 0.5 - 80), autoselect=True, button_type='square', label=ba.Lstr(resource='gatherWindow.inviteFriendsText'), on_activate_call=ba.WeakCall(self._google_invites)) ba.textwidget(parent=self._root_widget, size=(0, 0), position=(self._width * 0.5, self._height * 0.5 - 94), autoselect=True, scale=0.9, h_align='center', v_align='center', color=(0.5, 0.5, 0.5), flatness=1.0, text=or_text) ba.buttonwidget( parent=self._root_widget, size=(180, 50), position=(self._width * 0.5 - 90, self._height * 0.5 - 170), autoselect=True, color=(0.5, 0.5, 0.6), textcolor=(0.7, 0.7, 0.8), text_scale=0.8, label=ba.Lstr(resource='gatherWindow.appInviteSendACodeText'), on_activate_call=ba.WeakCall(self._send_code)) # kick off a transaction to get our code _ba.add_transaction( { 'type': 'FRIEND_PROMO_CODE_REQUEST', 'ali': False, 'expire_time': time.time() + 20 }, callback=ba.WeakCall(self._on_code_result)) _ba.run_transactions()
def __init__(self, tournament_id: str, tournament_activity: ba.Activity = None, position: Tuple[float, float] = (0.0, 0.0), delegate: Any = None, scale: float = None, offset: Tuple[float, float] = (0.0, 0.0), on_close_call: Callable[[], Any] = None): # Needs some tidying. # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-statements ba.set_analytics_screen('Tournament Entry Window') self._tournament_id = tournament_id self._tournament_info = (ba.app.tournament_info[self._tournament_id]) # Set a few vars depending on the tourney fee. self._fee = self._tournament_info['fee'] self._allow_ads = self._tournament_info['allowAds'] if self._fee == 4: self._purchase_name = 'tournament_entry_4' self._purchase_price_name = 'price.tournament_entry_4' elif self._fee == 3: self._purchase_name = 'tournament_entry_3' self._purchase_price_name = 'price.tournament_entry_3' elif self._fee == 2: self._purchase_name = 'tournament_entry_2' self._purchase_price_name = 'price.tournament_entry_2' elif self._fee == 1: self._purchase_name = 'tournament_entry_1' self._purchase_price_name = 'price.tournament_entry_1' else: if self._fee != 0: raise ValueError('invalid fee: ' + str(self._fee)) self._purchase_name = 'tournament_entry_0' self._purchase_price_name = 'price.tournament_entry_0' self._purchase_price: Optional[int] = None self._on_close_call = on_close_call if scale is None: uiscale = ba.app.uiscale scale = (2.3 if uiscale is ba.UIScale.SMALL else 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) self._delegate = delegate self._transitioning_out = False self._tournament_activity = tournament_activity self._width = 340 self._height = 220 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, offset=offset, toolbar_visibility='menu_currency') self._last_ad_press_time = -9999.0 self._last_ticket_press_time = -9999.0 self._entering = False self._launched = False # Show the ad button only if we support ads *and* it has a level 1 fee. self._do_ad_btn = (_ba.has_video_ads() and self._allow_ads) x_offs = 0 if self._do_ad_btn else 85 self._cancel_button = ba.buttonwidget(parent=self.root_widget, position=(20, self._height - 30), size=(50, 50), scale=0.5, label='', color=bg_color, on_activate_call=self._on_cancel, autoselect=True, icon=ba.gettexture('crossOut'), iconscale=1.2) 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=ba.Lstr(resource='tournamentEntryText'), maxwidth=200, color=(1, 1, 1, 0.4)) btn = self._pay_with_tickets_button = ba.buttonwidget( parent=self.root_widget, position=(30 + x_offs, 60), autoselect=True, button_type='square', size=(120, 120), label='', on_activate_call=self._on_pay_with_tickets_press) self._ticket_img_pos = (50 + x_offs, 94) self._ticket_img_pos_free = (50 + x_offs, 80) self._ticket_img = ba.imagewidget(parent=self.root_widget, draw_controller=btn, size=(80, 80), position=self._ticket_img_pos, texture=ba.gettexture('tickets')) self._ticket_cost_text_position = (87 + x_offs, 88) self._ticket_cost_text_position_free = (87 + x_offs, 120) self._ticket_cost_text = ba.textwidget( parent=self.root_widget, draw_controller=btn, position=self._ticket_cost_text_position, size=(0, 0), h_align='center', v_align='center', scale=0.6, text='', maxwidth=95, color=(0, 1, 0)) self._free_plays_remaining_text = ba.textwidget( parent=self.root_widget, draw_controller=btn, position=(87 + x_offs, 78), size=(0, 0), h_align='center', v_align='center', scale=0.33, text='', maxwidth=95, color=(0, 0.8, 0)) self._pay_with_ad_btn: Optional[ba.Widget] if self._do_ad_btn: btn = self._pay_with_ad_btn = ba.buttonwidget( parent=self.root_widget, position=(190, 60), autoselect=True, button_type='square', size=(120, 120), label='', on_activate_call=self._on_pay_with_ad_press) self._pay_with_ad_img = ba.imagewidget(parent=self.root_widget, draw_controller=btn, size=(80, 80), position=(210, 94), texture=ba.gettexture('tv')) self._ad_text_position = (251, 88) self._ad_text_position_remaining = (251, 92) have_ad_tries_remaining = ( self._tournament_info['adTriesRemaining'] is not None) self._ad_text = ba.textwidget( parent=self.root_widget, draw_controller=btn, position=self._ad_text_position_remaining if have_ad_tries_remaining else self._ad_text_position, size=(0, 0), h_align='center', v_align='center', scale=0.6, text=ba.Lstr(resource='watchAVideoText', fallback_resource='watchAnAdText'), maxwidth=95, color=(0, 1, 0)) ad_plays_remaining_text = ( '' if not have_ad_tries_remaining else '' + str(self._tournament_info['adTriesRemaining'])) self._ad_plays_remaining_text = ba.textwidget( parent=self.root_widget, draw_controller=btn, position=(251, 78), size=(0, 0), h_align='center', v_align='center', scale=0.33, text=ad_plays_remaining_text, maxwidth=95, color=(0, 0.8, 0)) ba.textwidget(parent=self.root_widget, position=(self._width * 0.5, 120), size=(0, 0), h_align='center', v_align='center', scale=0.6, text=ba.Lstr(resource='orText', subs=[('${A}', ''), ('${B}', '')]), maxwidth=35, color=(1, 1, 1, 0.5)) else: self._pay_with_ad_btn = None self._get_tickets_button: Optional[ba.Widget] if not ba.app.toolbars: self._get_tickets_button = ba.buttonwidget( parent=self.root_widget, position=(self._width - 190 + 110, 15), autoselect=True, scale=0.6, size=(120, 60), textcolor=(0.2, 1, 0.2), label=ba.charstr(ba.SpecialChar.TICKET), color=(0.6, 0.4, 0.7), on_activate_call=self._on_get_tickets_press) else: self._get_tickets_button = None self._seconds_remaining = None ba.containerwidget(edit=self.root_widget, cancel_button=self._cancel_button) # Let's also ask the server for info about this tournament # (time remaining, etc) so we can show the user time remaining, # disallow entry if time has run out, etc. xoffs = 104 if ba.app.toolbars else 0 self._time_remaining_text = ba.textwidget(parent=self.root_widget, position=(70 + xoffs, 23), size=(0, 0), h_align='center', v_align='center', text='-', scale=0.65, maxwidth=100, flatness=1.0, color=(0.7, 0.7, 0.7)) self._time_remaining_label_text = ba.textwidget( parent=self.root_widget, position=(70 + xoffs, 40), size=(0, 0), h_align='center', v_align='center', text=ba.Lstr(resource='coopSelectWindow.timeRemainingText'), scale=0.45, flatness=1.0, maxwidth=100, color=(0.7, 0.7, 0.7)) self._last_query_time: Optional[float] = None # If there seems to be a relatively-recent valid cached info for this # tournament, use it. Otherwise we'll kick off a query ourselves. if (self._tournament_id in ba.app.tournament_info and ba.app.tournament_info[self._tournament_id]['valid'] and (ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) - ba.app.tournament_info[self._tournament_id]['timeReceived'] < 1000 * 60 * 5)): try: info = ba.app.tournament_info[self._tournament_id] self._seconds_remaining = max( 0, info['timeRemaining'] - int( (ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) - info['timeReceived']) / 1000)) self._have_valid_data = True self._last_query_time = ba.time(ba.TimeType.REAL) except Exception: ba.print_exception('error using valid tourney data') self._have_valid_data = False else: self._have_valid_data = False self._fg_state = ba.app.fg_state self._running_query = False self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), repeat=True, timetype=ba.TimeType.REAL) self._update() self._restore_state()
def on_begin(self) -> None: # pylint: disable=too-many-locals # pylint: disable=too-many-statements from bastd.actor.text import Text from bastd.actor.image import Image ba.set_analytics_screen('FreeForAll Score Screen') super().on_begin() y_base = 100.0 ts_h_offs = -305.0 tdelay = 1.0 scale = 1.2 spacing = 37.0 # We include name and previous score in the sort to reduce the amount # of random jumping around the list we do in cases of ties. player_order_prev = list(self.players) player_order_prev.sort( reverse=True, key=lambda p: ( p.team.sessionteam.customdata['previous_score'], p.getname(full=True), )) player_order = list(self.players) player_order.sort(reverse=True, key=lambda p: ( p.team.sessionteam.customdata['score'], p.team.sessionteam.customdata['score'], p.getname(full=True), )) v_offs = -74.0 + spacing * len(player_order_prev) * 0.5 delay1 = 1.3 + 0.1 delay2 = 2.9 + 0.1 delay3 = 2.9 + 0.1 order_change = player_order != player_order_prev if order_change: delay3 += 1.5 ba.timer(0.3, ba.Call(ba.playsound, self._score_display_sound)) results = self.settings_raw['results'] assert isinstance(results, ba.GameResults) self.show_player_scores(delay=0.001, results=results, scale=1.2, x_offset=-110.0) sound_times: Set[float] = set() def _scoretxt(text: str, x_offs: float, y_offs: float, highlight: bool, delay: float, extrascale: float, flash: bool = False) -> Text: return Text(text, position=(ts_h_offs + x_offs * scale, y_base + (y_offs + v_offs + 2.0) * scale), scale=scale * extrascale, color=((1.0, 0.7, 0.3, 1.0) if highlight else (0.7, 0.7, 0.7, 0.7)), h_align=Text.HAlign.RIGHT, transition=Text.Transition.IN_LEFT, transition_delay=tdelay + delay, flash=flash).autoretain() v_offs -= spacing slide_amt = 0.0 transtime = 0.250 transtime2 = 0.250 session = self.session assert isinstance(session, ba.FreeForAllSession) title = Text(ba.Lstr(resource='firstToSeriesText', subs=[('${COUNT}', str(session.get_ffa_series_length()))]), scale=1.05 * scale, position=(ts_h_offs - 0.0 * scale, y_base + (v_offs + 50.0) * scale), h_align=Text.HAlign.CENTER, color=(0.5, 0.5, 0.5, 0.5), transition=Text.Transition.IN_LEFT, transition_delay=tdelay).autoretain() v_offs -= 25 v_offs_start = v_offs ba.timer( tdelay + delay3, ba.WeakCall( self._safe_animate, title.position_combine, 'input0', { 0.0: ts_h_offs - 0.0 * scale, transtime2: ts_h_offs - (0.0 + slide_amt) * scale })) for i, player in enumerate(player_order_prev): v_offs_2 = v_offs_start - spacing * (player_order.index(player)) ba.timer(tdelay + 0.3, ba.Call(ba.playsound, self._score_display_sound_small)) if order_change: ba.timer(tdelay + delay2 + 0.1, ba.Call(ba.playsound, self._cymbal_sound)) img = Image(player.get_icon(), position=(ts_h_offs - 72.0 * scale, y_base + (v_offs + 15.0) * scale), scale=(30.0 * scale, 30.0 * scale), transition=Image.Transition.IN_LEFT, transition_delay=tdelay).autoretain() ba.timer( tdelay + delay2, ba.WeakCall( self._safe_animate, img.position_combine, 'input1', { 0: y_base + (v_offs + 15.0) * scale, transtime: y_base + (v_offs_2 + 15.0) * scale })) ba.timer( tdelay + delay3, ba.WeakCall( self._safe_animate, img.position_combine, 'input0', { 0: ts_h_offs - 72.0 * scale, transtime2: ts_h_offs - (72.0 + slide_amt) * scale })) txt = Text(ba.Lstr(value=player.getname(full=True)), maxwidth=130.0, scale=0.75 * scale, position=(ts_h_offs - 50.0 * scale, y_base + (v_offs + 15.0) * scale), h_align=Text.HAlign.LEFT, v_align=Text.VAlign.CENTER, color=ba.safecolor(player.team.color + (1, )), transition=Text.Transition.IN_LEFT, transition_delay=tdelay).autoretain() ba.timer( tdelay + delay2, ba.WeakCall( self._safe_animate, txt.position_combine, 'input1', { 0: y_base + (v_offs + 15.0) * scale, transtime: y_base + (v_offs_2 + 15.0) * scale })) ba.timer( tdelay + delay3, ba.WeakCall( self._safe_animate, txt.position_combine, 'input0', { 0: ts_h_offs - 50.0 * scale, transtime2: ts_h_offs - (50.0 + slide_amt) * scale })) txt_num = Text('#' + str(i + 1), scale=0.55 * scale, position=(ts_h_offs - 95.0 * scale, y_base + (v_offs + 8.0) * scale), h_align=Text.HAlign.RIGHT, color=(0.6, 0.6, 0.6, 0.6), transition=Text.Transition.IN_LEFT, transition_delay=tdelay).autoretain() ba.timer( tdelay + delay3, ba.WeakCall( self._safe_animate, txt_num.position_combine, 'input0', { 0: ts_h_offs - 95.0 * scale, transtime2: ts_h_offs - (95.0 + slide_amt) * scale })) s_txt = _scoretxt( str(player.team.sessionteam.customdata['previous_score']), 80, 0, False, 0, 1.0) ba.timer( tdelay + delay2, ba.WeakCall( self._safe_animate, s_txt.position_combine, 'input1', { 0: y_base + (v_offs + 2.0) * scale, transtime: y_base + (v_offs_2 + 2.0) * scale })) ba.timer( tdelay + delay3, ba.WeakCall( self._safe_animate, s_txt.position_combine, 'input0', { 0: ts_h_offs + 80.0 * scale, transtime2: ts_h_offs + (80.0 - slide_amt) * scale })) score_change = ( player.team.sessionteam.customdata['score'] - player.team.sessionteam.customdata['previous_score']) if score_change > 0: xval = 113 yval = 3.0 s_txt_2 = _scoretxt('+' + str(score_change), xval, yval, True, 0, 0.7, flash=True) ba.timer( tdelay + delay2, ba.WeakCall( self._safe_animate, s_txt_2.position_combine, 'input1', { 0: y_base + (v_offs + yval + 2.0) * scale, transtime: y_base + (v_offs_2 + yval + 2.0) * scale })) ba.timer( tdelay + delay3, ba.WeakCall( self._safe_animate, s_txt_2.position_combine, 'input0', { 0: ts_h_offs + xval * scale, transtime2: ts_h_offs + (xval - slide_amt) * scale })) def _safesetattr(node: Optional[ba.Node], attr: str, value: Any) -> None: if node: setattr(node, attr, value) ba.timer( tdelay + delay1, ba.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1))) for j in range(score_change): ba.timer((tdelay + delay1 + 0.15 * j), ba.Call( _safesetattr, s_txt.node, 'text', str(player.team.sessionteam. customdata['previous_score'] + j + 1))) tfin = tdelay + delay1 + 0.15 * j if tfin not in sound_times: sound_times.add(tfin) ba.timer( tfin, ba.Call(ba.playsound, self._score_display_sound_small)) v_offs -= spacing
def __init__(self, transition: str = 'in_right', modal: bool = False, show_tab: str = None, on_close_call: Callable[[], Any] = None, back_location: str = None, origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # pylint: disable=too-many-locals from bastd.ui import tabs from ba import SpecialChar app = ba.app uiscale = app.uiscale ba.set_analytics_screen('Store Window') scale_origin: Optional[Tuple[float, float]] # If they provided an origin-widget, scale up from that. 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.button_infos: Optional[Dict[str, Dict[str, Any]]] = None self.update_buttons_timer: Optional[ba.Timer] = None self._status_textwidget_update_timer = None self._back_location = back_location self._on_close_call = on_close_call self._show_tab = show_tab self._modal = modal self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 self._x_inset = x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = (578 if uiscale is ba.UIScale.SMALL else 645 if uiscale is ba.UIScale.MEDIUM else 800) self._current_tab: Optional[str] = None extra_top = 30 if uiscale is ba.UIScale.SMALL else 0 self._request: Any = None self._r = 'store' self._last_buy_time: Optional[float] = None super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height + extra_top), transition=transition, toolbar_visibility='menu_full', scale=(1.3 if uiscale is ba.UIScale.SMALL else 0.9 if uiscale is ba.UIScale.MEDIUM else 0.8), scale_origin_stack_offset=scale_origin, stack_offset=((0, -5) if uiscale is ba.UIScale.SMALL else ( 0, 0) if uiscale is ba.UIScale.MEDIUM else (0, 0)))) self._back_button = btn = ba.buttonwidget( parent=self._root_widget, position=(70 + x_inset, self._height - 74), size=(140, 60), scale=1.1, autoselect=True, label=ba.Lstr(resource='doneText' if self._modal else 'backText'), button_type=None if self._modal else 'back', on_activate_call=self._back) ba.containerwidget(edit=self._root_widget, cancel_button=btn) self._get_tickets_button = ba.buttonwidget( parent=self._root_widget, size=(210, 65), on_activate_call=self._on_get_more_tickets_press, autoselect=True, scale=0.9, text_scale=1.4, left_widget=self._back_button, color=(0.7, 0.5, 0.85), textcolor=(0.2, 1.0, 0.2), label=ba.Lstr(resource='getTicketsWindow.titleText')) # Move this dynamically to keep it out of the way of the party icon. self._update_get_tickets_button_pos() self._get_ticket_pos_update_timer = ba.Timer( 1.0, ba.WeakCall(self._update_get_tickets_button_pos), repeat=True, timetype=ba.TimeType.REAL) ba.widget(edit=self._back_button, right_widget=self._get_tickets_button) 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() app = ba.app if app.platform in ['mac', 'ios'] and app.subplatform == 'appstore': ba.buttonwidget( parent=self._root_widget, position=(self._width * 0.5 - 70, 16), size=(230, 50), scale=0.65, on_activate_call=ba.WeakCall(self._restore_purchases), color=(0.35, 0.3, 0.4), selectable=False, textcolor=(0.55, 0.5, 0.6), label=ba.Lstr( resource='getTicketsWindow.restorePurchasesText')) ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height - 44), size=(0, 0), color=app.title_color, scale=1.5, h_align='center', v_align='center', text=ba.Lstr(resource='storeText'), maxwidth=420) if not self._modal: ba.buttonwidget(edit=self._back_button, button_type='backSmall', size=(60, 60), label=ba.charstr(SpecialChar.BACK)) scroll_buffer_h = 130 + 2 * x_inset tab_buffer_h = 250 + 2 * x_inset tabs_def = [ ('extras', ba.Lstr(resource=self._r + '.extrasText')), ('maps', ba.Lstr(resource=self._r + '.mapsText')), ('minigames', ba.Lstr(resource=self._r + '.miniGamesText')), ('characters', ba.Lstr(resource=self._r + '.charactersText')), ('icons', ba.Lstr(resource=self._r + '.iconsText')), ] tab_results = tabs.create_tab_buttons(self._root_widget, tabs_def, pos=(tab_buffer_h * 0.5, self._height - 130), size=(self._width - tab_buffer_h, 50), on_select_call=self._set_tab, return_extra_info=True) self._purchasable_count_widgets: Dict[str, Dict[str, Any]] = {} # Create our purchasable-items tags and have them update over time. for i, tab in enumerate(tabs_def): pos = tab_results['positions'][i] size = tab_results['sizes'][i] button = tab_results['buttons_indexed'][i] rad = 10 center = (pos[0] + 0.1 * size[0], pos[1] + 0.9 * size[1]) img = ba.imagewidget(parent=self._root_widget, position=(center[0] - rad * 1.04, center[1] - rad * 1.15), size=(rad * 2.2, rad * 2.2), texture=ba.gettexture('circleShadow'), color=(1, 0, 0)) txt = ba.textwidget(parent=self._root_widget, position=center, size=(0, 0), h_align='center', v_align='center', maxwidth=1.4 * rad, scale=0.6, shadow=1.0, flatness=1.0) rad = 20 sale_img = ba.imagewidget(parent=self._root_widget, position=(center[0] - rad, center[1] - rad), size=(rad * 2, rad * 2), draw_controller=button, texture=ba.gettexture('circleZigZag'), color=(0.5, 0, 1.0)) sale_title_text = ba.textwidget(parent=self._root_widget, position=(center[0], center[1] + 0.24 * rad), size=(0, 0), h_align='center', v_align='center', draw_controller=button, maxwidth=1.4 * rad, scale=0.6, shadow=0.0, flatness=1.0, color=(0, 1, 0)) sale_time_text = ba.textwidget(parent=self._root_widget, position=(center[0], center[1] - 0.29 * rad), size=(0, 0), h_align='center', v_align='center', draw_controller=button, maxwidth=1.4 * rad, scale=0.4, shadow=0.0, flatness=1.0, color=(0, 1, 0)) self._purchasable_count_widgets[tab[0]] = { 'img': img, 'text': txt, 'sale_img': sale_img, 'sale_title_text': sale_title_text, 'sale_time_text': sale_time_text } self._tab_update_timer = ba.Timer(1.0, ba.WeakCall(self._update_tabs), timetype=ba.TimeType.REAL, repeat=True) self._update_tabs() self._tab_buttons = tab_results['buttons'] if self._get_tickets_button is not None: last_tab_button = self._tab_buttons[tabs_def[-1][0]] ba.widget(edit=self._get_tickets_button, down_widget=last_tab_button) ba.widget(edit=last_tab_button, up_widget=self._get_tickets_button, right_widget=self._get_tickets_button) self._scroll_width = self._width - scroll_buffer_h self._scroll_height = self._height - 180 self._scrollwidget: Optional[ba.Widget] = None self._status_textwidget: Optional[ba.Widget] = None self._restore_state()
def __init__(self, sessiontype: type[ba.Session], transition: Optional[str] = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # pylint: disable=cyclic-import from bastd.ui.playlist import PlaylistTypeVars # 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 # Store state for when we exit the next game. if issubclass(sessiontype, ba.DualTeamSession): ba.app.ui.set_main_menu_location('Team Game Select') ba.set_analytics_screen('Teams Window') elif issubclass(sessiontype, ba.FreeForAllSession): ba.app.ui.set_main_menu_location('Free-for-All Game Select') ba.set_analytics_screen('FreeForAll Window') else: raise TypeError(f'Invalid sessiontype: {sessiontype}.') self._pvars = PlaylistTypeVars(sessiontype) self._sessiontype = sessiontype self._customize_button: Optional[ba.Widget] = None self._sub_width: Optional[float] = None self._sub_height: Optional[float] = None self._ensure_standard_playlists_exist() # Get the current selection (if any). self._selected_playlist = ba.app.config.get(self._pvars.config_name + ' Playlist Selection') uiscale = ba.app.ui.uiscale self._width = 900 if uiscale is ba.UIScale.SMALL else 800 x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 self._height = (480 if uiscale is ba.UIScale.SMALL else 510 if uiscale is ba.UIScale.MEDIUM else 580) 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, toolbar_visibility='menu_full', scale_origin_stack_offset=scale_origin, scale=(1.69 if uiscale is ba.UIScale.SMALL else 1.05 if uiscale is ba.UIScale.MEDIUM else 0.9), stack_offset=(0, -26) if uiscale is ba.UIScale.SMALL else (0, 0))) self._back_button: Optional[ba.Widget] = ba.buttonwidget( parent=self._root_widget, position=(59 + x_inset, self._height - 70), size=(120, 60), scale=1.0, on_activate_call=self._on_back_press, autoselect=True, label=ba.Lstr(resource='backText'), button_type='back') ba.containerwidget(edit=self._root_widget, cancel_button=self._back_button) txt = self._title_text = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height - 41), size=(0, 0), text=self._pvars.window_title_name, scale=1.3, res_scale=1.5, color=ba.app.ui.heading_color, h_align='center', v_align='center') if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: ba.textwidget(edit=txt, text='') ba.buttonwidget(edit=self._back_button, button_type='backSmall', size=(60, 54), position=(59 + x_inset, self._height - 67), label=ba.charstr(ba.SpecialChar.BACK)) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: self._back_button.delete() self._back_button = None ba.containerwidget(edit=self._root_widget, on_cancel_call=self._on_back_press) scroll_offs = 33 else: scroll_offs = 0 self._scroll_width = self._width - (100 + 2 * x_inset) self._scroll_height = (self._height - (146 if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars else 136)) self._scrollwidget = ba.scrollwidget( parent=self._root_widget, highlight=False, size=(self._scroll_width, self._scroll_height), position=((self._width - self._scroll_width) * 0.5, 65 + scroll_offs)) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) self._subcontainer: Optional[ba.Widget] = None self._config_name_full = self._pvars.config_name + ' Playlists' self._last_config = None # Update now and once per second. # (this should do our initial refresh) self._update() self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True)