bastd.ui.settings.advanced
UI functionality for advanced settings.
1# Released under the MIT License. See LICENSE for details. 2# 3"""UI functionality for advanced settings.""" 4 5from __future__ import annotations 6 7from typing import TYPE_CHECKING 8 9import _ba 10import ba 11from bastd.ui import popup as popup_ui 12 13if TYPE_CHECKING: 14 from typing import Any 15 16 17class AdvancedSettingsWindow(ba.Window): 18 """Window for editing advanced game settings.""" 19 20 def __init__(self, 21 transition: str = 'in_right', 22 origin_widget: ba.Widget | None = None): 23 # pylint: disable=too-many-statements 24 from ba.internal import master_server_get 25 import threading 26 27 # Preload some modules we use in a background thread so we won't 28 # have a visual hitch when the user taps them. 29 threading.Thread(target=self._preload_modules).start() 30 31 app = ba.app 32 33 # If they provided an origin-widget, scale up from that. 34 scale_origin: tuple[float, float] | None 35 if origin_widget is not None: 36 self._transition_out = 'out_scale' 37 scale_origin = origin_widget.get_screen_space_center() 38 transition = 'in_scale' 39 else: 40 self._transition_out = 'out_right' 41 scale_origin = None 42 43 uiscale = ba.app.ui.uiscale 44 self._width = 870.0 if uiscale is ba.UIScale.SMALL else 670.0 45 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 46 self._height = (390.0 if uiscale is ba.UIScale.SMALL else 47 450.0 if uiscale is ba.UIScale.MEDIUM else 520.0) 48 self._spacing = 32 49 self._menu_open = False 50 top_extra = 10 if uiscale is ba.UIScale.SMALL else 0 51 super().__init__(root_widget=ba.containerwidget( 52 size=(self._width, self._height + top_extra), 53 transition=transition, 54 toolbar_visibility='menu_minimal', 55 scale_origin_stack_offset=scale_origin, 56 scale=(2.06 if uiscale is ba.UIScale.SMALL else 57 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), 58 stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0))) 59 60 self._prev_lang = '' 61 self._prev_lang_list: list[str] = [] 62 self._complete_langs_list: list | None = None 63 self._complete_langs_error = False 64 self._language_popup: popup_ui.PopupMenu | None = None 65 66 # In vr-mode, the internal keyboard is currently the *only* option, 67 # so no need to show this. 68 self._show_always_use_internal_keyboard = (not app.vr_mode 69 and not app.iircade_mode) 70 71 self._scroll_width = self._width - (100 + 2 * x_inset) 72 self._scroll_height = self._height - 115.0 73 self._sub_width = self._scroll_width * 0.95 74 self._sub_height = 724.0 75 76 if self._show_always_use_internal_keyboard: 77 self._sub_height += 62 78 79 self._show_disable_gyro = app.platform in {'ios', 'android'} 80 if self._show_disable_gyro: 81 self._sub_height += 42 82 83 self._do_vr_test_button = app.vr_mode 84 self._do_net_test_button = True 85 self._extra_button_spacing = self._spacing * 2.5 86 87 if self._do_vr_test_button: 88 self._sub_height += self._extra_button_spacing 89 if self._do_net_test_button: 90 self._sub_height += self._extra_button_spacing 91 self._sub_height += self._spacing * 2.0 # plugins 92 93 self._r = 'settingsWindowAdvanced' 94 95 if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: 96 ba.containerwidget(edit=self._root_widget, 97 on_cancel_call=self._do_back) 98 self._back_button = None 99 else: 100 self._back_button = ba.buttonwidget( 101 parent=self._root_widget, 102 position=(53 + x_inset, self._height - 60), 103 size=(140, 60), 104 scale=0.8, 105 autoselect=True, 106 label=ba.Lstr(resource='backText'), 107 button_type='back', 108 on_activate_call=self._do_back) 109 ba.containerwidget(edit=self._root_widget, 110 cancel_button=self._back_button) 111 112 self._title_text = ba.textwidget(parent=self._root_widget, 113 position=(0, self._height - 52), 114 size=(self._width, 25), 115 text=ba.Lstr(resource=self._r + 116 '.titleText'), 117 color=app.ui.title_color, 118 h_align='center', 119 v_align='top') 120 121 if self._back_button is not None: 122 ba.buttonwidget(edit=self._back_button, 123 button_type='backSmall', 124 size=(60, 60), 125 label=ba.charstr(ba.SpecialChar.BACK)) 126 127 self._scrollwidget = ba.scrollwidget(parent=self._root_widget, 128 position=(50 + x_inset, 50), 129 simple_culling_v=20.0, 130 highlight=False, 131 size=(self._scroll_width, 132 self._scroll_height), 133 selection_loops_to_parent=True) 134 ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) 135 self._subcontainer = ba.containerwidget(parent=self._scrollwidget, 136 size=(self._sub_width, 137 self._sub_height), 138 background=False, 139 selection_loops_to_parent=True) 140 141 self._rebuild() 142 143 # Rebuild periodically to pick up language changes/additions/etc. 144 self._rebuild_timer = ba.Timer(1.0, 145 ba.WeakCall(self._rebuild), 146 repeat=True, 147 timetype=ba.TimeType.REAL) 148 149 # Fetch the list of completed languages. 150 master_server_get('bsLangGetCompleted', {'b': app.build_number}, 151 callback=ba.WeakCall(self._completed_langs_cb)) 152 153 # noinspection PyUnresolvedReferences 154 @staticmethod 155 def _preload_modules() -> None: 156 """Preload modules we use (called in bg thread).""" 157 from bastd.ui import config as _unused1 158 from ba import modutils as _unused2 159 from bastd.ui.settings import vrtesting as _unused3 160 from bastd.ui.settings import nettesting as _unused4 161 from bastd.ui import appinvite as _unused5 162 from bastd.ui import account as _unused6 163 from bastd.ui import promocode as _unused7 164 from bastd.ui import debug as _unused8 165 from bastd.ui.settings import plugins as _unused9 166 167 def _update_lang_status(self) -> None: 168 if self._complete_langs_list is not None: 169 up_to_date = (ba.app.lang.language in self._complete_langs_list) 170 ba.textwidget( 171 edit=self._lang_status_text, 172 text='' if ba.app.lang.language == 'Test' else ba.Lstr( 173 resource=self._r + '.translationNoUpdateNeededText') 174 if up_to_date else ba.Lstr(resource=self._r + 175 '.translationUpdateNeededText'), 176 color=(0.2, 1.0, 0.2, 0.8) if up_to_date else 177 (1.0, 0.2, 0.2, 0.8)) 178 else: 179 ba.textwidget( 180 edit=self._lang_status_text, 181 text=ba.Lstr(resource=self._r + '.translationFetchErrorText') 182 if self._complete_langs_error else ba.Lstr( 183 resource=self._r + '.translationFetchingStatusText'), 184 color=(1.0, 0.5, 0.2) if self._complete_langs_error else 185 (0.7, 0.7, 0.7)) 186 187 def _rebuild(self) -> None: 188 # pylint: disable=too-many-statements 189 # pylint: disable=too-many-branches 190 # pylint: disable=too-many-locals 191 from bastd.ui.config import ConfigCheckBox 192 from ba.modutils import show_user_scripts 193 194 available_languages = ba.app.lang.available_languages 195 196 # Don't rebuild if the menu is open or if our language and 197 # language-list hasn't changed. 198 # NOTE - although we now support widgets updating their own 199 # translations, we still change the label formatting on the language 200 # menu based on the language so still need this. ...however we could 201 # make this more limited to it only rebuilds that one menu instead 202 # of everything. 203 if self._menu_open or (self._prev_lang == _ba.app.config.get( 204 'Lang', None) and self._prev_lang_list == available_languages): 205 return 206 self._prev_lang = _ba.app.config.get('Lang', None) 207 self._prev_lang_list = available_languages 208 209 # Clear out our sub-container. 210 children = self._subcontainer.get_children() 211 for child in children: 212 child.delete() 213 214 v = self._sub_height - 35 215 216 v -= self._spacing * 1.2 217 218 # Update our existing back button and title. 219 if self._back_button is not None: 220 ba.buttonwidget(edit=self._back_button, 221 label=ba.Lstr(resource='backText')) 222 ba.buttonwidget(edit=self._back_button, 223 label=ba.charstr(ba.SpecialChar.BACK)) 224 225 ba.textwidget(edit=self._title_text, 226 text=ba.Lstr(resource=self._r + '.titleText')) 227 228 this_button_width = 410 229 230 self._promo_code_button = ba.buttonwidget( 231 parent=self._subcontainer, 232 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 233 size=(this_button_width, 60), 234 autoselect=True, 235 label=ba.Lstr(resource=self._r + '.enterPromoCodeText'), 236 text_scale=1.0, 237 on_activate_call=self._on_promo_code_press) 238 if self._back_button is not None: 239 ba.widget(edit=self._promo_code_button, 240 up_widget=self._back_button, 241 left_widget=self._back_button) 242 v -= self._extra_button_spacing * 0.8 243 244 ba.textwidget(parent=self._subcontainer, 245 position=(200, v + 10), 246 size=(0, 0), 247 text=ba.Lstr(resource=self._r + '.languageText'), 248 maxwidth=150, 249 scale=0.95, 250 color=ba.app.ui.title_color, 251 h_align='right', 252 v_align='center') 253 254 languages = _ba.app.lang.available_languages 255 cur_lang = _ba.app.config.get('Lang', None) 256 if cur_lang is None: 257 cur_lang = 'Auto' 258 259 # We have a special dict of language names in that language 260 # so we don't have to go digging through each full language. 261 try: 262 import json 263 with open('ba_data/data/langdata.json', 264 encoding='utf-8') as infile: 265 lang_names_translated = (json.loads( 266 infile.read())['lang_names_translated']) 267 except Exception: 268 ba.print_exception('Error reading lang data.') 269 lang_names_translated = {} 270 271 langs_translated = {} 272 for lang in languages: 273 langs_translated[lang] = lang_names_translated.get(lang, lang) 274 275 langs_full = {} 276 for lang in languages: 277 lang_translated = ba.Lstr(translate=('languages', lang)).evaluate() 278 if langs_translated[lang] == lang_translated: 279 langs_full[lang] = lang_translated 280 else: 281 langs_full[lang] = (langs_translated[lang] + ' (' + 282 lang_translated + ')') 283 284 self._language_popup = popup_ui.PopupMenu( 285 parent=self._subcontainer, 286 position=(210, v - 19), 287 width=150, 288 opening_call=ba.WeakCall(self._on_menu_open), 289 closing_call=ba.WeakCall(self._on_menu_close), 290 autoselect=False, 291 on_value_change_call=ba.WeakCall(self._on_menu_choice), 292 choices=['Auto'] + languages, 293 button_size=(250, 60), 294 choices_display=([ 295 ba.Lstr(value=(ba.Lstr(resource='autoText').evaluate() + ' (' + 296 ba.Lstr(translate=('languages', 297 ba.app.lang.default_language 298 )).evaluate() + ')')) 299 ] + [ba.Lstr(value=langs_full[l]) for l in languages]), 300 current_choice=cur_lang) 301 302 v -= self._spacing * 1.8 303 304 ba.textwidget(parent=self._subcontainer, 305 position=(self._sub_width * 0.5, v + 10), 306 size=(0, 0), 307 text=ba.Lstr(resource=self._r + '.helpTranslateText', 308 subs=[('${APP_NAME}', 309 ba.Lstr(resource='titleText'))]), 310 maxwidth=self._sub_width * 0.9, 311 max_height=55, 312 flatness=1.0, 313 scale=0.65, 314 color=(0.4, 0.9, 0.4, 0.8), 315 h_align='center', 316 v_align='center') 317 v -= self._spacing * 1.9 318 this_button_width = 410 319 self._translation_editor_button = ba.buttonwidget( 320 parent=self._subcontainer, 321 position=(self._sub_width / 2 - this_button_width / 2, v - 24), 322 size=(this_button_width, 60), 323 label=ba.Lstr(resource=self._r + '.translationEditorButtonText', 324 subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) 325 ]), 326 autoselect=True, 327 on_activate_call=ba.Call( 328 ba.open_url, 'https://legacy.ballistica.net/translate')) 329 330 self._lang_status_text = ba.textwidget(parent=self._subcontainer, 331 position=(self._sub_width * 0.5, 332 v - 40), 333 size=(0, 0), 334 text='', 335 flatness=1.0, 336 scale=0.63, 337 h_align='center', 338 v_align='center', 339 maxwidth=400.0) 340 self._update_lang_status() 341 v -= 40 342 343 lang_inform = _ba.get_v1_account_misc_val('langInform', False) 344 345 self._language_inform_checkbox = cbw = ba.checkboxwidget( 346 parent=self._subcontainer, 347 position=(50, v - 50), 348 size=(self._sub_width - 100, 30), 349 autoselect=True, 350 maxwidth=430, 351 textcolor=(0.8, 0.8, 0.8), 352 value=lang_inform, 353 text=ba.Lstr(resource=self._r + '.translationInformMe'), 354 on_value_change_call=ba.WeakCall( 355 self._on_lang_inform_value_change)) 356 357 ba.widget(edit=self._translation_editor_button, 358 down_widget=cbw, 359 up_widget=self._language_popup.get_button()) 360 361 v -= self._spacing * 3.0 362 363 self._kick_idle_players_check_box = ConfigCheckBox( 364 parent=self._subcontainer, 365 position=(50, v), 366 size=(self._sub_width - 100, 30), 367 configkey='Kick Idle Players', 368 displayname=ba.Lstr(resource=self._r + '.kickIdlePlayersText'), 369 scale=1.0, 370 maxwidth=430) 371 372 v -= 42 373 self._disable_camera_shake_check_box = ConfigCheckBox( 374 parent=self._subcontainer, 375 position=(50, v), 376 size=(self._sub_width - 100, 30), 377 configkey='Disable Camera Shake', 378 displayname=ba.Lstr(resource=self._r + '.disableCameraShakeText'), 379 scale=1.0, 380 maxwidth=430) 381 382 self._disable_gyro_check_box: ConfigCheckBox | None = None 383 if self._show_disable_gyro: 384 v -= 42 385 self._disable_gyro_check_box = ConfigCheckBox( 386 parent=self._subcontainer, 387 position=(50, v), 388 size=(self._sub_width - 100, 30), 389 configkey='Disable Camera Gyro', 390 displayname=ba.Lstr(resource=self._r + 391 '.disableCameraGyroscopeMotionText'), 392 scale=1.0, 393 maxwidth=430) 394 395 self._always_use_internal_keyboard_check_box: ConfigCheckBox | None 396 if self._show_always_use_internal_keyboard: 397 v -= 42 398 self._always_use_internal_keyboard_check_box = ConfigCheckBox( 399 parent=self._subcontainer, 400 position=(50, v), 401 size=(self._sub_width - 100, 30), 402 configkey='Always Use Internal Keyboard', 403 autoselect=True, 404 displayname=ba.Lstr(resource=self._r + 405 '.alwaysUseInternalKeyboardText'), 406 scale=1.0, 407 maxwidth=430) 408 ba.textwidget( 409 parent=self._subcontainer, 410 position=(90, v - 10), 411 size=(0, 0), 412 text=ba.Lstr(resource=self._r + 413 '.alwaysUseInternalKeyboardDescriptionText'), 414 maxwidth=400, 415 flatness=1.0, 416 scale=0.65, 417 color=(0.4, 0.9, 0.4, 0.8), 418 h_align='left', 419 v_align='center') 420 v -= 20 421 else: 422 self._always_use_internal_keyboard_check_box = None 423 424 v -= self._spacing * 2.1 425 426 this_button_width = 410 427 self._modding_guide_button = ba.buttonwidget( 428 parent=self._subcontainer, 429 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 430 size=(this_button_width, 60), 431 autoselect=True, 432 label=ba.Lstr(resource=self._r + '.moddingGuideText'), 433 text_scale=1.0, 434 on_activate_call=ba.Call( 435 ba.open_url, 'http://ballistica.net/wiki/modding-guide')) 436 if self._show_always_use_internal_keyboard: 437 assert self._always_use_internal_keyboard_check_box is not None 438 ba.widget(edit=self._always_use_internal_keyboard_check_box.widget, 439 down_widget=self._modding_guide_button) 440 ba.widget( 441 edit=self._modding_guide_button, 442 up_widget=self._always_use_internal_keyboard_check_box.widget) 443 else: 444 ba.widget(edit=self._modding_guide_button, 445 up_widget=self._kick_idle_players_check_box.widget) 446 ba.widget(edit=self._kick_idle_players_check_box.widget, 447 down_widget=self._modding_guide_button) 448 449 v -= self._spacing * 2.0 450 451 self._show_user_mods_button = ba.buttonwidget( 452 parent=self._subcontainer, 453 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 454 size=(this_button_width, 60), 455 autoselect=True, 456 label=ba.Lstr(resource=self._r + '.showUserModsText'), 457 text_scale=1.0, 458 on_activate_call=show_user_scripts) 459 460 v -= self._spacing * 2.0 461 462 self._plugins_button = ba.buttonwidget( 463 parent=self._subcontainer, 464 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 465 size=(this_button_width, 60), 466 autoselect=True, 467 label=ba.Lstr(resource='pluginsText'), 468 text_scale=1.0, 469 on_activate_call=self._on_plugins_button_press) 470 471 v -= self._spacing * 0.6 472 473 self._vr_test_button: ba.Widget | None 474 if self._do_vr_test_button: 475 v -= self._extra_button_spacing 476 self._vr_test_button = ba.buttonwidget( 477 parent=self._subcontainer, 478 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 479 size=(this_button_width, 60), 480 autoselect=True, 481 label=ba.Lstr(resource=self._r + '.vrTestingText'), 482 text_scale=1.0, 483 on_activate_call=self._on_vr_test_press) 484 else: 485 self._vr_test_button = None 486 487 self._net_test_button: ba.Widget | None 488 if self._do_net_test_button: 489 v -= self._extra_button_spacing 490 self._net_test_button = ba.buttonwidget( 491 parent=self._subcontainer, 492 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 493 size=(this_button_width, 60), 494 autoselect=True, 495 label=ba.Lstr(resource=self._r + '.netTestingText'), 496 text_scale=1.0, 497 on_activate_call=self._on_net_test_press) 498 else: 499 self._net_test_button = None 500 501 v -= 70 502 self._benchmarks_button = ba.buttonwidget( 503 parent=self._subcontainer, 504 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 505 size=(this_button_width, 60), 506 autoselect=True, 507 label=ba.Lstr(resource=self._r + '.benchmarksText'), 508 text_scale=1.0, 509 on_activate_call=self._on_benchmark_press) 510 511 for child in self._subcontainer.get_children(): 512 ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20) 513 514 if ba.app.ui.use_toolbars: 515 pbtn = _ba.get_special_widget('party_button') 516 ba.widget(edit=self._scrollwidget, right_widget=pbtn) 517 if self._back_button is None: 518 ba.widget(edit=self._scrollwidget, 519 left_widget=_ba.get_special_widget('back_button')) 520 521 self._restore_state() 522 523 def _show_restart_needed(self, value: Any) -> None: 524 del value # Unused. 525 ba.screenmessage(ba.Lstr(resource=self._r + '.mustRestartText'), 526 color=(1, 1, 0)) 527 528 def _on_lang_inform_value_change(self, val: bool) -> None: 529 _ba.add_transaction({ 530 'type': 'SET_MISC_VAL', 531 'name': 'langInform', 532 'value': val 533 }) 534 _ba.run_transactions() 535 536 def _on_vr_test_press(self) -> None: 537 from bastd.ui.settings.vrtesting import VRTestingWindow 538 self._save_state() 539 ba.containerwidget(edit=self._root_widget, transition='out_left') 540 ba.app.ui.set_main_menu_window( 541 VRTestingWindow(transition='in_right').get_root_widget()) 542 543 def _on_net_test_press(self) -> None: 544 from bastd.ui.settings.nettesting import NetTestingWindow 545 546 # Net-testing requires a signed in v1 account. 547 if _ba.get_v1_account_state() != 'signed_in': 548 ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'), 549 color=(1, 0, 0)) 550 ba.playsound(ba.getsound('error')) 551 return 552 553 self._save_state() 554 ba.containerwidget(edit=self._root_widget, transition='out_left') 555 ba.app.ui.set_main_menu_window( 556 NetTestingWindow(transition='in_right').get_root_widget()) 557 558 def _on_friend_promo_code_press(self) -> None: 559 from bastd.ui import appinvite 560 from bastd.ui import account 561 if _ba.get_v1_account_state() != 'signed_in': 562 account.show_sign_in_prompt() 563 return 564 appinvite.handle_app_invites_press() 565 566 def _on_plugins_button_press(self) -> None: 567 from bastd.ui.settings.plugins import PluginSettingsWindow 568 self._save_state() 569 ba.containerwidget(edit=self._root_widget, transition='out_left') 570 ba.app.ui.set_main_menu_window( 571 PluginSettingsWindow( 572 origin_widget=self._plugins_button).get_root_widget()) 573 574 def _on_promo_code_press(self) -> None: 575 from bastd.ui.promocode import PromoCodeWindow 576 from bastd.ui.account import show_sign_in_prompt 577 578 # We have to be logged in for promo-codes to work. 579 if _ba.get_v1_account_state() != 'signed_in': 580 show_sign_in_prompt() 581 return 582 self._save_state() 583 ba.containerwidget(edit=self._root_widget, transition='out_left') 584 ba.app.ui.set_main_menu_window( 585 PromoCodeWindow( 586 origin_widget=self._promo_code_button).get_root_widget()) 587 588 def _on_benchmark_press(self) -> None: 589 from bastd.ui.debug import DebugWindow 590 self._save_state() 591 ba.containerwidget(edit=self._root_widget, transition='out_left') 592 ba.app.ui.set_main_menu_window( 593 DebugWindow(transition='in_right').get_root_widget()) 594 595 def _save_state(self) -> None: 596 # pylint: disable=too-many-branches 597 try: 598 sel = self._root_widget.get_selected_child() 599 if sel == self._scrollwidget: 600 sel = self._subcontainer.get_selected_child() 601 if sel == self._vr_test_button: 602 sel_name = 'VRTest' 603 elif sel == self._net_test_button: 604 sel_name = 'NetTest' 605 elif sel == self._promo_code_button: 606 sel_name = 'PromoCode' 607 elif sel == self._benchmarks_button: 608 sel_name = 'Benchmarks' 609 elif sel == self._kick_idle_players_check_box.widget: 610 sel_name = 'KickIdlePlayers' 611 elif sel == self._disable_camera_shake_check_box.widget: 612 sel_name = 'DisableCameraShake' 613 elif (self._always_use_internal_keyboard_check_box is not None 614 and sel 615 == self._always_use_internal_keyboard_check_box.widget): 616 sel_name = 'AlwaysUseInternalKeyboard' 617 elif (self._disable_gyro_check_box is not None 618 and sel == self._disable_gyro_check_box.widget): 619 sel_name = 'DisableGyro' 620 elif (self._language_popup is not None 621 and sel == self._language_popup.get_button()): 622 sel_name = 'Languages' 623 elif sel == self._translation_editor_button: 624 sel_name = 'TranslationEditor' 625 elif sel == self._show_user_mods_button: 626 sel_name = 'ShowUserMods' 627 elif sel == self._plugins_button: 628 sel_name = 'Plugins' 629 elif sel == self._modding_guide_button: 630 sel_name = 'ModdingGuide' 631 elif sel == self._language_inform_checkbox: 632 sel_name = 'LangInform' 633 else: 634 raise ValueError(f'unrecognized selection \'{sel}\'') 635 elif sel == self._back_button: 636 sel_name = 'Back' 637 else: 638 raise ValueError(f'unrecognized selection \'{sel}\'') 639 ba.app.ui.window_states[type(self)] = {'sel_name': sel_name} 640 except Exception: 641 ba.print_exception(f'Error saving state for {self.__class__}') 642 643 def _restore_state(self) -> None: 644 # pylint: disable=too-many-branches 645 try: 646 sel_name = ba.app.ui.window_states.get(type(self), 647 {}).get('sel_name') 648 if sel_name == 'Back': 649 sel = self._back_button 650 else: 651 ba.containerwidget(edit=self._root_widget, 652 selected_child=self._scrollwidget) 653 if sel_name == 'VRTest': 654 sel = self._vr_test_button 655 elif sel_name == 'NetTest': 656 sel = self._net_test_button 657 elif sel_name == 'PromoCode': 658 sel = self._promo_code_button 659 elif sel_name == 'Benchmarks': 660 sel = self._benchmarks_button 661 elif sel_name == 'KickIdlePlayers': 662 sel = self._kick_idle_players_check_box.widget 663 elif sel_name == 'DisableCameraShake': 664 sel = self._disable_camera_shake_check_box.widget 665 elif (sel_name == 'AlwaysUseInternalKeyboard' 666 and self._always_use_internal_keyboard_check_box 667 is not None): 668 sel = self._always_use_internal_keyboard_check_box.widget 669 elif (sel_name == 'DisableGyro' 670 and self._disable_gyro_check_box is not None): 671 sel = self._disable_gyro_check_box.widget 672 elif (sel_name == 'Languages' 673 and self._language_popup is not None): 674 sel = self._language_popup.get_button() 675 elif sel_name == 'TranslationEditor': 676 sel = self._translation_editor_button 677 elif sel_name == 'ShowUserMods': 678 sel = self._show_user_mods_button 679 elif sel_name == 'Plugins': 680 sel = self._plugins_button 681 elif sel_name == 'ModdingGuide': 682 sel = self._modding_guide_button 683 elif sel_name == 'LangInform': 684 sel = self._language_inform_checkbox 685 else: 686 sel = None 687 if sel is not None: 688 ba.containerwidget(edit=self._subcontainer, 689 selected_child=sel, 690 visible_child=sel) 691 except Exception: 692 ba.print_exception(f'Error restoring state for {self.__class__}') 693 694 def _on_menu_open(self) -> None: 695 self._menu_open = True 696 697 def _on_menu_close(self) -> None: 698 self._menu_open = False 699 700 def _on_menu_choice(self, choice: str) -> None: 701 ba.app.lang.setlanguage(None if choice == 'Auto' else choice) 702 self._save_state() 703 ba.timer(0.1, ba.WeakCall(self._rebuild), timetype=ba.TimeType.REAL) 704 705 def _completed_langs_cb(self, results: dict[str, Any] | None) -> None: 706 if results is not None and results['langs'] is not None: 707 self._complete_langs_list = results['langs'] 708 self._complete_langs_error = False 709 else: 710 self._complete_langs_list = None 711 self._complete_langs_error = True 712 ba.timer(0.001, 713 ba.WeakCall(self._update_lang_status), 714 timetype=ba.TimeType.REAL) 715 716 def _do_back(self) -> None: 717 from bastd.ui.settings.allsettings import AllSettingsWindow 718 self._save_state() 719 ba.containerwidget(edit=self._root_widget, 720 transition=self._transition_out) 721 ba.app.ui.set_main_menu_window( 722 AllSettingsWindow(transition='in_left').get_root_widget())
class
AdvancedSettingsWindow(ba.ui.Window):
18class AdvancedSettingsWindow(ba.Window): 19 """Window for editing advanced game settings.""" 20 21 def __init__(self, 22 transition: str = 'in_right', 23 origin_widget: ba.Widget | None = None): 24 # pylint: disable=too-many-statements 25 from ba.internal import master_server_get 26 import threading 27 28 # Preload some modules we use in a background thread so we won't 29 # have a visual hitch when the user taps them. 30 threading.Thread(target=self._preload_modules).start() 31 32 app = ba.app 33 34 # If they provided an origin-widget, scale up from that. 35 scale_origin: tuple[float, float] | None 36 if origin_widget is not None: 37 self._transition_out = 'out_scale' 38 scale_origin = origin_widget.get_screen_space_center() 39 transition = 'in_scale' 40 else: 41 self._transition_out = 'out_right' 42 scale_origin = None 43 44 uiscale = ba.app.ui.uiscale 45 self._width = 870.0 if uiscale is ba.UIScale.SMALL else 670.0 46 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 47 self._height = (390.0 if uiscale is ba.UIScale.SMALL else 48 450.0 if uiscale is ba.UIScale.MEDIUM else 520.0) 49 self._spacing = 32 50 self._menu_open = False 51 top_extra = 10 if uiscale is ba.UIScale.SMALL else 0 52 super().__init__(root_widget=ba.containerwidget( 53 size=(self._width, self._height + top_extra), 54 transition=transition, 55 toolbar_visibility='menu_minimal', 56 scale_origin_stack_offset=scale_origin, 57 scale=(2.06 if uiscale is ba.UIScale.SMALL else 58 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), 59 stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0))) 60 61 self._prev_lang = '' 62 self._prev_lang_list: list[str] = [] 63 self._complete_langs_list: list | None = None 64 self._complete_langs_error = False 65 self._language_popup: popup_ui.PopupMenu | None = None 66 67 # In vr-mode, the internal keyboard is currently the *only* option, 68 # so no need to show this. 69 self._show_always_use_internal_keyboard = (not app.vr_mode 70 and not app.iircade_mode) 71 72 self._scroll_width = self._width - (100 + 2 * x_inset) 73 self._scroll_height = self._height - 115.0 74 self._sub_width = self._scroll_width * 0.95 75 self._sub_height = 724.0 76 77 if self._show_always_use_internal_keyboard: 78 self._sub_height += 62 79 80 self._show_disable_gyro = app.platform in {'ios', 'android'} 81 if self._show_disable_gyro: 82 self._sub_height += 42 83 84 self._do_vr_test_button = app.vr_mode 85 self._do_net_test_button = True 86 self._extra_button_spacing = self._spacing * 2.5 87 88 if self._do_vr_test_button: 89 self._sub_height += self._extra_button_spacing 90 if self._do_net_test_button: 91 self._sub_height += self._extra_button_spacing 92 self._sub_height += self._spacing * 2.0 # plugins 93 94 self._r = 'settingsWindowAdvanced' 95 96 if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: 97 ba.containerwidget(edit=self._root_widget, 98 on_cancel_call=self._do_back) 99 self._back_button = None 100 else: 101 self._back_button = ba.buttonwidget( 102 parent=self._root_widget, 103 position=(53 + x_inset, self._height - 60), 104 size=(140, 60), 105 scale=0.8, 106 autoselect=True, 107 label=ba.Lstr(resource='backText'), 108 button_type='back', 109 on_activate_call=self._do_back) 110 ba.containerwidget(edit=self._root_widget, 111 cancel_button=self._back_button) 112 113 self._title_text = ba.textwidget(parent=self._root_widget, 114 position=(0, self._height - 52), 115 size=(self._width, 25), 116 text=ba.Lstr(resource=self._r + 117 '.titleText'), 118 color=app.ui.title_color, 119 h_align='center', 120 v_align='top') 121 122 if self._back_button is not None: 123 ba.buttonwidget(edit=self._back_button, 124 button_type='backSmall', 125 size=(60, 60), 126 label=ba.charstr(ba.SpecialChar.BACK)) 127 128 self._scrollwidget = ba.scrollwidget(parent=self._root_widget, 129 position=(50 + x_inset, 50), 130 simple_culling_v=20.0, 131 highlight=False, 132 size=(self._scroll_width, 133 self._scroll_height), 134 selection_loops_to_parent=True) 135 ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) 136 self._subcontainer = ba.containerwidget(parent=self._scrollwidget, 137 size=(self._sub_width, 138 self._sub_height), 139 background=False, 140 selection_loops_to_parent=True) 141 142 self._rebuild() 143 144 # Rebuild periodically to pick up language changes/additions/etc. 145 self._rebuild_timer = ba.Timer(1.0, 146 ba.WeakCall(self._rebuild), 147 repeat=True, 148 timetype=ba.TimeType.REAL) 149 150 # Fetch the list of completed languages. 151 master_server_get('bsLangGetCompleted', {'b': app.build_number}, 152 callback=ba.WeakCall(self._completed_langs_cb)) 153 154 # noinspection PyUnresolvedReferences 155 @staticmethod 156 def _preload_modules() -> None: 157 """Preload modules we use (called in bg thread).""" 158 from bastd.ui import config as _unused1 159 from ba import modutils as _unused2 160 from bastd.ui.settings import vrtesting as _unused3 161 from bastd.ui.settings import nettesting as _unused4 162 from bastd.ui import appinvite as _unused5 163 from bastd.ui import account as _unused6 164 from bastd.ui import promocode as _unused7 165 from bastd.ui import debug as _unused8 166 from bastd.ui.settings import plugins as _unused9 167 168 def _update_lang_status(self) -> None: 169 if self._complete_langs_list is not None: 170 up_to_date = (ba.app.lang.language in self._complete_langs_list) 171 ba.textwidget( 172 edit=self._lang_status_text, 173 text='' if ba.app.lang.language == 'Test' else ba.Lstr( 174 resource=self._r + '.translationNoUpdateNeededText') 175 if up_to_date else ba.Lstr(resource=self._r + 176 '.translationUpdateNeededText'), 177 color=(0.2, 1.0, 0.2, 0.8) if up_to_date else 178 (1.0, 0.2, 0.2, 0.8)) 179 else: 180 ba.textwidget( 181 edit=self._lang_status_text, 182 text=ba.Lstr(resource=self._r + '.translationFetchErrorText') 183 if self._complete_langs_error else ba.Lstr( 184 resource=self._r + '.translationFetchingStatusText'), 185 color=(1.0, 0.5, 0.2) if self._complete_langs_error else 186 (0.7, 0.7, 0.7)) 187 188 def _rebuild(self) -> None: 189 # pylint: disable=too-many-statements 190 # pylint: disable=too-many-branches 191 # pylint: disable=too-many-locals 192 from bastd.ui.config import ConfigCheckBox 193 from ba.modutils import show_user_scripts 194 195 available_languages = ba.app.lang.available_languages 196 197 # Don't rebuild if the menu is open or if our language and 198 # language-list hasn't changed. 199 # NOTE - although we now support widgets updating their own 200 # translations, we still change the label formatting on the language 201 # menu based on the language so still need this. ...however we could 202 # make this more limited to it only rebuilds that one menu instead 203 # of everything. 204 if self._menu_open or (self._prev_lang == _ba.app.config.get( 205 'Lang', None) and self._prev_lang_list == available_languages): 206 return 207 self._prev_lang = _ba.app.config.get('Lang', None) 208 self._prev_lang_list = available_languages 209 210 # Clear out our sub-container. 211 children = self._subcontainer.get_children() 212 for child in children: 213 child.delete() 214 215 v = self._sub_height - 35 216 217 v -= self._spacing * 1.2 218 219 # Update our existing back button and title. 220 if self._back_button is not None: 221 ba.buttonwidget(edit=self._back_button, 222 label=ba.Lstr(resource='backText')) 223 ba.buttonwidget(edit=self._back_button, 224 label=ba.charstr(ba.SpecialChar.BACK)) 225 226 ba.textwidget(edit=self._title_text, 227 text=ba.Lstr(resource=self._r + '.titleText')) 228 229 this_button_width = 410 230 231 self._promo_code_button = ba.buttonwidget( 232 parent=self._subcontainer, 233 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 234 size=(this_button_width, 60), 235 autoselect=True, 236 label=ba.Lstr(resource=self._r + '.enterPromoCodeText'), 237 text_scale=1.0, 238 on_activate_call=self._on_promo_code_press) 239 if self._back_button is not None: 240 ba.widget(edit=self._promo_code_button, 241 up_widget=self._back_button, 242 left_widget=self._back_button) 243 v -= self._extra_button_spacing * 0.8 244 245 ba.textwidget(parent=self._subcontainer, 246 position=(200, v + 10), 247 size=(0, 0), 248 text=ba.Lstr(resource=self._r + '.languageText'), 249 maxwidth=150, 250 scale=0.95, 251 color=ba.app.ui.title_color, 252 h_align='right', 253 v_align='center') 254 255 languages = _ba.app.lang.available_languages 256 cur_lang = _ba.app.config.get('Lang', None) 257 if cur_lang is None: 258 cur_lang = 'Auto' 259 260 # We have a special dict of language names in that language 261 # so we don't have to go digging through each full language. 262 try: 263 import json 264 with open('ba_data/data/langdata.json', 265 encoding='utf-8') as infile: 266 lang_names_translated = (json.loads( 267 infile.read())['lang_names_translated']) 268 except Exception: 269 ba.print_exception('Error reading lang data.') 270 lang_names_translated = {} 271 272 langs_translated = {} 273 for lang in languages: 274 langs_translated[lang] = lang_names_translated.get(lang, lang) 275 276 langs_full = {} 277 for lang in languages: 278 lang_translated = ba.Lstr(translate=('languages', lang)).evaluate() 279 if langs_translated[lang] == lang_translated: 280 langs_full[lang] = lang_translated 281 else: 282 langs_full[lang] = (langs_translated[lang] + ' (' + 283 lang_translated + ')') 284 285 self._language_popup = popup_ui.PopupMenu( 286 parent=self._subcontainer, 287 position=(210, v - 19), 288 width=150, 289 opening_call=ba.WeakCall(self._on_menu_open), 290 closing_call=ba.WeakCall(self._on_menu_close), 291 autoselect=False, 292 on_value_change_call=ba.WeakCall(self._on_menu_choice), 293 choices=['Auto'] + languages, 294 button_size=(250, 60), 295 choices_display=([ 296 ba.Lstr(value=(ba.Lstr(resource='autoText').evaluate() + ' (' + 297 ba.Lstr(translate=('languages', 298 ba.app.lang.default_language 299 )).evaluate() + ')')) 300 ] + [ba.Lstr(value=langs_full[l]) for l in languages]), 301 current_choice=cur_lang) 302 303 v -= self._spacing * 1.8 304 305 ba.textwidget(parent=self._subcontainer, 306 position=(self._sub_width * 0.5, v + 10), 307 size=(0, 0), 308 text=ba.Lstr(resource=self._r + '.helpTranslateText', 309 subs=[('${APP_NAME}', 310 ba.Lstr(resource='titleText'))]), 311 maxwidth=self._sub_width * 0.9, 312 max_height=55, 313 flatness=1.0, 314 scale=0.65, 315 color=(0.4, 0.9, 0.4, 0.8), 316 h_align='center', 317 v_align='center') 318 v -= self._spacing * 1.9 319 this_button_width = 410 320 self._translation_editor_button = ba.buttonwidget( 321 parent=self._subcontainer, 322 position=(self._sub_width / 2 - this_button_width / 2, v - 24), 323 size=(this_button_width, 60), 324 label=ba.Lstr(resource=self._r + '.translationEditorButtonText', 325 subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) 326 ]), 327 autoselect=True, 328 on_activate_call=ba.Call( 329 ba.open_url, 'https://legacy.ballistica.net/translate')) 330 331 self._lang_status_text = ba.textwidget(parent=self._subcontainer, 332 position=(self._sub_width * 0.5, 333 v - 40), 334 size=(0, 0), 335 text='', 336 flatness=1.0, 337 scale=0.63, 338 h_align='center', 339 v_align='center', 340 maxwidth=400.0) 341 self._update_lang_status() 342 v -= 40 343 344 lang_inform = _ba.get_v1_account_misc_val('langInform', False) 345 346 self._language_inform_checkbox = cbw = ba.checkboxwidget( 347 parent=self._subcontainer, 348 position=(50, v - 50), 349 size=(self._sub_width - 100, 30), 350 autoselect=True, 351 maxwidth=430, 352 textcolor=(0.8, 0.8, 0.8), 353 value=lang_inform, 354 text=ba.Lstr(resource=self._r + '.translationInformMe'), 355 on_value_change_call=ba.WeakCall( 356 self._on_lang_inform_value_change)) 357 358 ba.widget(edit=self._translation_editor_button, 359 down_widget=cbw, 360 up_widget=self._language_popup.get_button()) 361 362 v -= self._spacing * 3.0 363 364 self._kick_idle_players_check_box = ConfigCheckBox( 365 parent=self._subcontainer, 366 position=(50, v), 367 size=(self._sub_width - 100, 30), 368 configkey='Kick Idle Players', 369 displayname=ba.Lstr(resource=self._r + '.kickIdlePlayersText'), 370 scale=1.0, 371 maxwidth=430) 372 373 v -= 42 374 self._disable_camera_shake_check_box = ConfigCheckBox( 375 parent=self._subcontainer, 376 position=(50, v), 377 size=(self._sub_width - 100, 30), 378 configkey='Disable Camera Shake', 379 displayname=ba.Lstr(resource=self._r + '.disableCameraShakeText'), 380 scale=1.0, 381 maxwidth=430) 382 383 self._disable_gyro_check_box: ConfigCheckBox | None = None 384 if self._show_disable_gyro: 385 v -= 42 386 self._disable_gyro_check_box = ConfigCheckBox( 387 parent=self._subcontainer, 388 position=(50, v), 389 size=(self._sub_width - 100, 30), 390 configkey='Disable Camera Gyro', 391 displayname=ba.Lstr(resource=self._r + 392 '.disableCameraGyroscopeMotionText'), 393 scale=1.0, 394 maxwidth=430) 395 396 self._always_use_internal_keyboard_check_box: ConfigCheckBox | None 397 if self._show_always_use_internal_keyboard: 398 v -= 42 399 self._always_use_internal_keyboard_check_box = ConfigCheckBox( 400 parent=self._subcontainer, 401 position=(50, v), 402 size=(self._sub_width - 100, 30), 403 configkey='Always Use Internal Keyboard', 404 autoselect=True, 405 displayname=ba.Lstr(resource=self._r + 406 '.alwaysUseInternalKeyboardText'), 407 scale=1.0, 408 maxwidth=430) 409 ba.textwidget( 410 parent=self._subcontainer, 411 position=(90, v - 10), 412 size=(0, 0), 413 text=ba.Lstr(resource=self._r + 414 '.alwaysUseInternalKeyboardDescriptionText'), 415 maxwidth=400, 416 flatness=1.0, 417 scale=0.65, 418 color=(0.4, 0.9, 0.4, 0.8), 419 h_align='left', 420 v_align='center') 421 v -= 20 422 else: 423 self._always_use_internal_keyboard_check_box = None 424 425 v -= self._spacing * 2.1 426 427 this_button_width = 410 428 self._modding_guide_button = ba.buttonwidget( 429 parent=self._subcontainer, 430 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 431 size=(this_button_width, 60), 432 autoselect=True, 433 label=ba.Lstr(resource=self._r + '.moddingGuideText'), 434 text_scale=1.0, 435 on_activate_call=ba.Call( 436 ba.open_url, 'http://ballistica.net/wiki/modding-guide')) 437 if self._show_always_use_internal_keyboard: 438 assert self._always_use_internal_keyboard_check_box is not None 439 ba.widget(edit=self._always_use_internal_keyboard_check_box.widget, 440 down_widget=self._modding_guide_button) 441 ba.widget( 442 edit=self._modding_guide_button, 443 up_widget=self._always_use_internal_keyboard_check_box.widget) 444 else: 445 ba.widget(edit=self._modding_guide_button, 446 up_widget=self._kick_idle_players_check_box.widget) 447 ba.widget(edit=self._kick_idle_players_check_box.widget, 448 down_widget=self._modding_guide_button) 449 450 v -= self._spacing * 2.0 451 452 self._show_user_mods_button = ba.buttonwidget( 453 parent=self._subcontainer, 454 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 455 size=(this_button_width, 60), 456 autoselect=True, 457 label=ba.Lstr(resource=self._r + '.showUserModsText'), 458 text_scale=1.0, 459 on_activate_call=show_user_scripts) 460 461 v -= self._spacing * 2.0 462 463 self._plugins_button = ba.buttonwidget( 464 parent=self._subcontainer, 465 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 466 size=(this_button_width, 60), 467 autoselect=True, 468 label=ba.Lstr(resource='pluginsText'), 469 text_scale=1.0, 470 on_activate_call=self._on_plugins_button_press) 471 472 v -= self._spacing * 0.6 473 474 self._vr_test_button: ba.Widget | None 475 if self._do_vr_test_button: 476 v -= self._extra_button_spacing 477 self._vr_test_button = ba.buttonwidget( 478 parent=self._subcontainer, 479 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 480 size=(this_button_width, 60), 481 autoselect=True, 482 label=ba.Lstr(resource=self._r + '.vrTestingText'), 483 text_scale=1.0, 484 on_activate_call=self._on_vr_test_press) 485 else: 486 self._vr_test_button = None 487 488 self._net_test_button: ba.Widget | None 489 if self._do_net_test_button: 490 v -= self._extra_button_spacing 491 self._net_test_button = ba.buttonwidget( 492 parent=self._subcontainer, 493 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 494 size=(this_button_width, 60), 495 autoselect=True, 496 label=ba.Lstr(resource=self._r + '.netTestingText'), 497 text_scale=1.0, 498 on_activate_call=self._on_net_test_press) 499 else: 500 self._net_test_button = None 501 502 v -= 70 503 self._benchmarks_button = ba.buttonwidget( 504 parent=self._subcontainer, 505 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 506 size=(this_button_width, 60), 507 autoselect=True, 508 label=ba.Lstr(resource=self._r + '.benchmarksText'), 509 text_scale=1.0, 510 on_activate_call=self._on_benchmark_press) 511 512 for child in self._subcontainer.get_children(): 513 ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20) 514 515 if ba.app.ui.use_toolbars: 516 pbtn = _ba.get_special_widget('party_button') 517 ba.widget(edit=self._scrollwidget, right_widget=pbtn) 518 if self._back_button is None: 519 ba.widget(edit=self._scrollwidget, 520 left_widget=_ba.get_special_widget('back_button')) 521 522 self._restore_state() 523 524 def _show_restart_needed(self, value: Any) -> None: 525 del value # Unused. 526 ba.screenmessage(ba.Lstr(resource=self._r + '.mustRestartText'), 527 color=(1, 1, 0)) 528 529 def _on_lang_inform_value_change(self, val: bool) -> None: 530 _ba.add_transaction({ 531 'type': 'SET_MISC_VAL', 532 'name': 'langInform', 533 'value': val 534 }) 535 _ba.run_transactions() 536 537 def _on_vr_test_press(self) -> None: 538 from bastd.ui.settings.vrtesting import VRTestingWindow 539 self._save_state() 540 ba.containerwidget(edit=self._root_widget, transition='out_left') 541 ba.app.ui.set_main_menu_window( 542 VRTestingWindow(transition='in_right').get_root_widget()) 543 544 def _on_net_test_press(self) -> None: 545 from bastd.ui.settings.nettesting import NetTestingWindow 546 547 # Net-testing requires a signed in v1 account. 548 if _ba.get_v1_account_state() != 'signed_in': 549 ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'), 550 color=(1, 0, 0)) 551 ba.playsound(ba.getsound('error')) 552 return 553 554 self._save_state() 555 ba.containerwidget(edit=self._root_widget, transition='out_left') 556 ba.app.ui.set_main_menu_window( 557 NetTestingWindow(transition='in_right').get_root_widget()) 558 559 def _on_friend_promo_code_press(self) -> None: 560 from bastd.ui import appinvite 561 from bastd.ui import account 562 if _ba.get_v1_account_state() != 'signed_in': 563 account.show_sign_in_prompt() 564 return 565 appinvite.handle_app_invites_press() 566 567 def _on_plugins_button_press(self) -> None: 568 from bastd.ui.settings.plugins import PluginSettingsWindow 569 self._save_state() 570 ba.containerwidget(edit=self._root_widget, transition='out_left') 571 ba.app.ui.set_main_menu_window( 572 PluginSettingsWindow( 573 origin_widget=self._plugins_button).get_root_widget()) 574 575 def _on_promo_code_press(self) -> None: 576 from bastd.ui.promocode import PromoCodeWindow 577 from bastd.ui.account import show_sign_in_prompt 578 579 # We have to be logged in for promo-codes to work. 580 if _ba.get_v1_account_state() != 'signed_in': 581 show_sign_in_prompt() 582 return 583 self._save_state() 584 ba.containerwidget(edit=self._root_widget, transition='out_left') 585 ba.app.ui.set_main_menu_window( 586 PromoCodeWindow( 587 origin_widget=self._promo_code_button).get_root_widget()) 588 589 def _on_benchmark_press(self) -> None: 590 from bastd.ui.debug import DebugWindow 591 self._save_state() 592 ba.containerwidget(edit=self._root_widget, transition='out_left') 593 ba.app.ui.set_main_menu_window( 594 DebugWindow(transition='in_right').get_root_widget()) 595 596 def _save_state(self) -> None: 597 # pylint: disable=too-many-branches 598 try: 599 sel = self._root_widget.get_selected_child() 600 if sel == self._scrollwidget: 601 sel = self._subcontainer.get_selected_child() 602 if sel == self._vr_test_button: 603 sel_name = 'VRTest' 604 elif sel == self._net_test_button: 605 sel_name = 'NetTest' 606 elif sel == self._promo_code_button: 607 sel_name = 'PromoCode' 608 elif sel == self._benchmarks_button: 609 sel_name = 'Benchmarks' 610 elif sel == self._kick_idle_players_check_box.widget: 611 sel_name = 'KickIdlePlayers' 612 elif sel == self._disable_camera_shake_check_box.widget: 613 sel_name = 'DisableCameraShake' 614 elif (self._always_use_internal_keyboard_check_box is not None 615 and sel 616 == self._always_use_internal_keyboard_check_box.widget): 617 sel_name = 'AlwaysUseInternalKeyboard' 618 elif (self._disable_gyro_check_box is not None 619 and sel == self._disable_gyro_check_box.widget): 620 sel_name = 'DisableGyro' 621 elif (self._language_popup is not None 622 and sel == self._language_popup.get_button()): 623 sel_name = 'Languages' 624 elif sel == self._translation_editor_button: 625 sel_name = 'TranslationEditor' 626 elif sel == self._show_user_mods_button: 627 sel_name = 'ShowUserMods' 628 elif sel == self._plugins_button: 629 sel_name = 'Plugins' 630 elif sel == self._modding_guide_button: 631 sel_name = 'ModdingGuide' 632 elif sel == self._language_inform_checkbox: 633 sel_name = 'LangInform' 634 else: 635 raise ValueError(f'unrecognized selection \'{sel}\'') 636 elif sel == self._back_button: 637 sel_name = 'Back' 638 else: 639 raise ValueError(f'unrecognized selection \'{sel}\'') 640 ba.app.ui.window_states[type(self)] = {'sel_name': sel_name} 641 except Exception: 642 ba.print_exception(f'Error saving state for {self.__class__}') 643 644 def _restore_state(self) -> None: 645 # pylint: disable=too-many-branches 646 try: 647 sel_name = ba.app.ui.window_states.get(type(self), 648 {}).get('sel_name') 649 if sel_name == 'Back': 650 sel = self._back_button 651 else: 652 ba.containerwidget(edit=self._root_widget, 653 selected_child=self._scrollwidget) 654 if sel_name == 'VRTest': 655 sel = self._vr_test_button 656 elif sel_name == 'NetTest': 657 sel = self._net_test_button 658 elif sel_name == 'PromoCode': 659 sel = self._promo_code_button 660 elif sel_name == 'Benchmarks': 661 sel = self._benchmarks_button 662 elif sel_name == 'KickIdlePlayers': 663 sel = self._kick_idle_players_check_box.widget 664 elif sel_name == 'DisableCameraShake': 665 sel = self._disable_camera_shake_check_box.widget 666 elif (sel_name == 'AlwaysUseInternalKeyboard' 667 and self._always_use_internal_keyboard_check_box 668 is not None): 669 sel = self._always_use_internal_keyboard_check_box.widget 670 elif (sel_name == 'DisableGyro' 671 and self._disable_gyro_check_box is not None): 672 sel = self._disable_gyro_check_box.widget 673 elif (sel_name == 'Languages' 674 and self._language_popup is not None): 675 sel = self._language_popup.get_button() 676 elif sel_name == 'TranslationEditor': 677 sel = self._translation_editor_button 678 elif sel_name == 'ShowUserMods': 679 sel = self._show_user_mods_button 680 elif sel_name == 'Plugins': 681 sel = self._plugins_button 682 elif sel_name == 'ModdingGuide': 683 sel = self._modding_guide_button 684 elif sel_name == 'LangInform': 685 sel = self._language_inform_checkbox 686 else: 687 sel = None 688 if sel is not None: 689 ba.containerwidget(edit=self._subcontainer, 690 selected_child=sel, 691 visible_child=sel) 692 except Exception: 693 ba.print_exception(f'Error restoring state for {self.__class__}') 694 695 def _on_menu_open(self) -> None: 696 self._menu_open = True 697 698 def _on_menu_close(self) -> None: 699 self._menu_open = False 700 701 def _on_menu_choice(self, choice: str) -> None: 702 ba.app.lang.setlanguage(None if choice == 'Auto' else choice) 703 self._save_state() 704 ba.timer(0.1, ba.WeakCall(self._rebuild), timetype=ba.TimeType.REAL) 705 706 def _completed_langs_cb(self, results: dict[str, Any] | None) -> None: 707 if results is not None and results['langs'] is not None: 708 self._complete_langs_list = results['langs'] 709 self._complete_langs_error = False 710 else: 711 self._complete_langs_list = None 712 self._complete_langs_error = True 713 ba.timer(0.001, 714 ba.WeakCall(self._update_lang_status), 715 timetype=ba.TimeType.REAL) 716 717 def _do_back(self) -> None: 718 from bastd.ui.settings.allsettings import AllSettingsWindow 719 self._save_state() 720 ba.containerwidget(edit=self._root_widget, 721 transition=self._transition_out) 722 ba.app.ui.set_main_menu_window( 723 AllSettingsWindow(transition='in_left').get_root_widget())
Window for editing advanced game settings.
AdvancedSettingsWindow( transition: str = 'in_right', origin_widget: _ba.Widget | None = None)
21 def __init__(self, 22 transition: str = 'in_right', 23 origin_widget: ba.Widget | None = None): 24 # pylint: disable=too-many-statements 25 from ba.internal import master_server_get 26 import threading 27 28 # Preload some modules we use in a background thread so we won't 29 # have a visual hitch when the user taps them. 30 threading.Thread(target=self._preload_modules).start() 31 32 app = ba.app 33 34 # If they provided an origin-widget, scale up from that. 35 scale_origin: tuple[float, float] | None 36 if origin_widget is not None: 37 self._transition_out = 'out_scale' 38 scale_origin = origin_widget.get_screen_space_center() 39 transition = 'in_scale' 40 else: 41 self._transition_out = 'out_right' 42 scale_origin = None 43 44 uiscale = ba.app.ui.uiscale 45 self._width = 870.0 if uiscale is ba.UIScale.SMALL else 670.0 46 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 47 self._height = (390.0 if uiscale is ba.UIScale.SMALL else 48 450.0 if uiscale is ba.UIScale.MEDIUM else 520.0) 49 self._spacing = 32 50 self._menu_open = False 51 top_extra = 10 if uiscale is ba.UIScale.SMALL else 0 52 super().__init__(root_widget=ba.containerwidget( 53 size=(self._width, self._height + top_extra), 54 transition=transition, 55 toolbar_visibility='menu_minimal', 56 scale_origin_stack_offset=scale_origin, 57 scale=(2.06 if uiscale is ba.UIScale.SMALL else 58 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), 59 stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0))) 60 61 self._prev_lang = '' 62 self._prev_lang_list: list[str] = [] 63 self._complete_langs_list: list | None = None 64 self._complete_langs_error = False 65 self._language_popup: popup_ui.PopupMenu | None = None 66 67 # In vr-mode, the internal keyboard is currently the *only* option, 68 # so no need to show this. 69 self._show_always_use_internal_keyboard = (not app.vr_mode 70 and not app.iircade_mode) 71 72 self._scroll_width = self._width - (100 + 2 * x_inset) 73 self._scroll_height = self._height - 115.0 74 self._sub_width = self._scroll_width * 0.95 75 self._sub_height = 724.0 76 77 if self._show_always_use_internal_keyboard: 78 self._sub_height += 62 79 80 self._show_disable_gyro = app.platform in {'ios', 'android'} 81 if self._show_disable_gyro: 82 self._sub_height += 42 83 84 self._do_vr_test_button = app.vr_mode 85 self._do_net_test_button = True 86 self._extra_button_spacing = self._spacing * 2.5 87 88 if self._do_vr_test_button: 89 self._sub_height += self._extra_button_spacing 90 if self._do_net_test_button: 91 self._sub_height += self._extra_button_spacing 92 self._sub_height += self._spacing * 2.0 # plugins 93 94 self._r = 'settingsWindowAdvanced' 95 96 if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: 97 ba.containerwidget(edit=self._root_widget, 98 on_cancel_call=self._do_back) 99 self._back_button = None 100 else: 101 self._back_button = ba.buttonwidget( 102 parent=self._root_widget, 103 position=(53 + x_inset, self._height - 60), 104 size=(140, 60), 105 scale=0.8, 106 autoselect=True, 107 label=ba.Lstr(resource='backText'), 108 button_type='back', 109 on_activate_call=self._do_back) 110 ba.containerwidget(edit=self._root_widget, 111 cancel_button=self._back_button) 112 113 self._title_text = ba.textwidget(parent=self._root_widget, 114 position=(0, self._height - 52), 115 size=(self._width, 25), 116 text=ba.Lstr(resource=self._r + 117 '.titleText'), 118 color=app.ui.title_color, 119 h_align='center', 120 v_align='top') 121 122 if self._back_button is not None: 123 ba.buttonwidget(edit=self._back_button, 124 button_type='backSmall', 125 size=(60, 60), 126 label=ba.charstr(ba.SpecialChar.BACK)) 127 128 self._scrollwidget = ba.scrollwidget(parent=self._root_widget, 129 position=(50 + x_inset, 50), 130 simple_culling_v=20.0, 131 highlight=False, 132 size=(self._scroll_width, 133 self._scroll_height), 134 selection_loops_to_parent=True) 135 ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) 136 self._subcontainer = ba.containerwidget(parent=self._scrollwidget, 137 size=(self._sub_width, 138 self._sub_height), 139 background=False, 140 selection_loops_to_parent=True) 141 142 self._rebuild() 143 144 # Rebuild periodically to pick up language changes/additions/etc. 145 self._rebuild_timer = ba.Timer(1.0, 146 ba.WeakCall(self._rebuild), 147 repeat=True, 148 timetype=ba.TimeType.REAL) 149 150 # Fetch the list of completed languages. 151 master_server_get('bsLangGetCompleted', {'b': app.build_number}, 152 callback=ba.WeakCall(self._completed_langs_cb))
Inherited Members
- ba.ui.Window
- get_root_widget