bauiv1lib.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 7import os 8import logging 9from typing import TYPE_CHECKING, override 10 11from bauiv1lib.popup import PopupMenu 12import bauiv1 as bui 13 14if TYPE_CHECKING: 15 from typing import Any 16 17 18class AdvancedSettingsWindow(bui.MainWindow): 19 """Window for editing advanced app settings.""" 20 21 def __init__( 22 self, 23 transition: str | None = 'in_right', 24 origin_widget: bui.Widget | None = None, 25 ): 26 # pylint: disable=too-many-statements 27 28 if bui.app.classic is None: 29 raise RuntimeError('This requires classic support.') 30 31 # Preload some modules we use in a background thread so we won't 32 # have a visual hitch when the user taps them. 33 bui.app.threadpool_submit_no_wait(self._preload_modules) 34 35 app = bui.app 36 assert app.classic is not None 37 38 uiscale = bui.app.ui_v1.uiscale 39 self._width = 1030.0 if uiscale is bui.UIScale.SMALL else 670.0 40 x_inset = 150 if uiscale is bui.UIScale.SMALL else 0 41 self._height = ( 42 390.0 43 if uiscale is bui.UIScale.SMALL 44 else 450.0 if uiscale is bui.UIScale.MEDIUM else 520.0 45 ) 46 self._lang_status_text: bui.Widget | None = None 47 48 self._spacing = 32 49 self._menu_open = False 50 top_extra = 10 if uiscale is bui.UIScale.SMALL else 0 51 52 super().__init__( 53 root_widget=bui.containerwidget( 54 size=(self._width, self._height + top_extra), 55 toolbar_visibility=( 56 'menu_minimal' 57 if uiscale is bui.UIScale.SMALL 58 else 'menu_full' 59 ), 60 scale=( 61 2.04 62 if uiscale is bui.UIScale.SMALL 63 else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0 64 ), 65 stack_offset=( 66 (0, 10) if uiscale is bui.UIScale.SMALL else (0, 0) 67 ), 68 ), 69 transition=transition, 70 origin_widget=origin_widget, 71 ) 72 73 self._prev_lang = '' 74 self._prev_lang_list: list[str] = [] 75 self._complete_langs_list: list | None = None 76 self._complete_langs_error = False 77 self._language_popup: PopupMenu | None = None 78 79 # In vr-mode, the internal keyboard is currently the *only* option, 80 # so no need to show this. 81 self._show_always_use_internal_keyboard = not app.env.vr 82 83 self._scroll_width = self._width - (100 + 2 * x_inset) 84 self._scroll_height = self._height - ( 85 125.0 if uiscale is bui.UIScale.SMALL else 115.0 86 ) 87 self._sub_width = self._scroll_width * 0.95 88 self._sub_height = 870.0 89 90 if self._show_always_use_internal_keyboard: 91 self._sub_height += 62 92 93 self._show_disable_gyro = app.classic.platform in {'ios', 'android'} 94 if self._show_disable_gyro: 95 self._sub_height += 42 96 97 self._do_vr_test_button = app.env.vr 98 self._do_net_test_button = True 99 self._extra_button_spacing = self._spacing * 2.5 100 101 if self._do_vr_test_button: 102 self._sub_height += self._extra_button_spacing 103 if self._do_net_test_button: 104 self._sub_height += self._extra_button_spacing 105 self._sub_height += self._spacing * 2.0 # plugins 106 self._sub_height += self._spacing * 2.0 # dev tools 107 108 self._r = 'settingsWindowAdvanced' 109 110 if uiscale is bui.UIScale.SMALL: 111 bui.containerwidget( 112 edit=self._root_widget, on_cancel_call=self.main_window_back 113 ) 114 self._back_button = None 115 else: 116 self._back_button = bui.buttonwidget( 117 parent=self._root_widget, 118 position=(53 + x_inset, self._height - 60), 119 size=(140, 60), 120 scale=0.8, 121 autoselect=True, 122 label=bui.Lstr(resource='backText'), 123 button_type='back', 124 on_activate_call=self.main_window_back, 125 ) 126 bui.containerwidget( 127 edit=self._root_widget, cancel_button=self._back_button 128 ) 129 130 self._title_text = bui.textwidget( 131 parent=self._root_widget, 132 position=( 133 self._width * 0.5, 134 self._height - (61 if uiscale is bui.UIScale.SMALL else 40), 135 ), 136 size=(0, 0), 137 scale=0.85 if uiscale is bui.UIScale.SMALL else 1.0, 138 text=bui.Lstr(resource=f'{self._r}.titleText'), 139 color=app.ui_v1.title_color, 140 h_align='center', 141 v_align='center', 142 ) 143 144 if self._back_button is not None: 145 bui.buttonwidget( 146 edit=self._back_button, 147 button_type='backSmall', 148 size=(60, 60), 149 label=bui.charstr(bui.SpecialChar.BACK), 150 ) 151 152 self._scrollwidget = bui.scrollwidget( 153 parent=self._root_widget, 154 position=(50 + x_inset, 50), 155 simple_culling_v=20.0, 156 highlight=False, 157 size=(self._scroll_width, self._scroll_height), 158 selection_loops_to_parent=True, 159 ) 160 bui.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) 161 self._subcontainer = bui.containerwidget( 162 parent=self._scrollwidget, 163 size=(self._sub_width, self._sub_height), 164 background=False, 165 selection_loops_to_parent=True, 166 ) 167 168 self._rebuild() 169 170 # Rebuild periodically to pick up language changes/additions/etc. 171 self._rebuild_timer = bui.AppTimer( 172 1.0, bui.WeakCall(self._rebuild), repeat=True 173 ) 174 175 # Fetch the list of completed languages. 176 bui.app.classic.master_server_v1_get( 177 'bsLangGetCompleted', 178 {'b': app.env.engine_build_number}, 179 callback=bui.WeakCall(self._completed_langs_cb), 180 ) 181 182 @override 183 def get_main_window_state(self) -> bui.MainWindowState: 184 # Support recreating our window for back/refresh purposes. 185 cls = type(self) 186 return bui.BasicMainWindowState( 187 create_call=lambda transition, origin_widget: cls( 188 transition=transition, origin_widget=origin_widget 189 ) 190 ) 191 192 @override 193 def on_main_window_close(self) -> None: 194 self._save_state() 195 196 @staticmethod 197 def _preload_modules() -> None: 198 """Preload stuff in bg thread to avoid hitches in logic thread""" 199 from babase import modutils as _unused2 200 from bauiv1lib import config as _unused1 201 from bauiv1lib.settings import vrtesting as _unused3 202 from bauiv1lib.settings import nettesting as _unused4 203 from bauiv1lib import appinvite as _unused5 204 from bauiv1lib import account as _unused6 205 from bauiv1lib import sendinfo as _unused7 206 from bauiv1lib.settings import benchmarks as _unused8 207 from bauiv1lib.settings import plugins as _unused9 208 from bauiv1lib.settings import devtools as _unused10 209 210 def _update_lang_status(self) -> None: 211 if self._complete_langs_list is not None: 212 up_to_date = bui.app.lang.language in self._complete_langs_list 213 bui.textwidget( 214 edit=self._lang_status_text, 215 text=( 216 '' 217 if bui.app.lang.language == 'Test' 218 else ( 219 bui.Lstr( 220 resource=f'{self._r}.translationNoUpdateNeededText' 221 ) 222 if up_to_date 223 else bui.Lstr( 224 resource=f'{self._r}.translationUpdateNeededText' 225 ) 226 ) 227 ), 228 color=( 229 (0.2, 1.0, 0.2, 0.8) if up_to_date else (1.0, 0.2, 0.2, 0.8) 230 ), 231 ) 232 else: 233 bui.textwidget( 234 edit=self._lang_status_text, 235 text=( 236 bui.Lstr(resource=f'{self._r}.translationFetchErrorText') 237 if self._complete_langs_error 238 else bui.Lstr( 239 resource=f'{self._r}.translationFetchingStatusText' 240 ) 241 ), 242 color=( 243 (1.0, 0.5, 0.2) 244 if self._complete_langs_error 245 else (0.7, 0.7, 0.7) 246 ), 247 ) 248 249 def _rebuild(self) -> None: 250 # pylint: disable=too-many-statements 251 # pylint: disable=too-many-branches 252 # pylint: disable=too-many-locals 253 254 from bauiv1lib.config import ConfigCheckBox 255 from babase.modutils import show_user_scripts 256 257 plus = bui.app.plus 258 assert plus is not None 259 260 available_languages = bui.app.lang.available_languages 261 262 # Don't rebuild if the menu is open or if our language and 263 # language-list hasn't changed. 264 265 # NOTE - although we now support widgets updating their own 266 # translations, we still change the label formatting on the language 267 # menu based on the language so still need this. ...however we could 268 # make this more limited to it only rebuilds that one menu instead 269 # of everything. 270 if self._menu_open or ( 271 self._prev_lang == bui.app.config.get('Lang', None) 272 and self._prev_lang_list == available_languages 273 ): 274 return 275 self._prev_lang = bui.app.config.get('Lang', None) 276 self._prev_lang_list = available_languages 277 278 # Clear out our sub-container. 279 children = self._subcontainer.get_children() 280 for child in children: 281 child.delete() 282 283 v = self._sub_height - 35 284 285 v -= self._spacing * 1.2 286 287 # Update our existing back button and title. 288 if self._back_button is not None: 289 bui.buttonwidget( 290 edit=self._back_button, label=bui.Lstr(resource='backText') 291 ) 292 bui.buttonwidget( 293 edit=self._back_button, label=bui.charstr(bui.SpecialChar.BACK) 294 ) 295 296 bui.textwidget( 297 edit=self._title_text, 298 text=bui.Lstr(resource=f'{self._r}.titleText'), 299 ) 300 301 this_button_width = 410 302 303 assert bui.app.classic is not None 304 bui.textwidget( 305 parent=self._subcontainer, 306 position=(70, v + 10), 307 size=(0, 0), 308 text=bui.Lstr(resource=f'{self._r}.languageText'), 309 maxwidth=150, 310 scale=1.2, 311 color=bui.app.ui_v1.title_color, 312 h_align='left', 313 v_align='center', 314 ) 315 316 languages = bui.app.lang.available_languages 317 cur_lang = bui.app.config.get('Lang', None) 318 if cur_lang is None: 319 cur_lang = 'Auto' 320 321 # We have a special dict of language names in that language 322 # so we don't have to go digging through each full language. 323 try: 324 import json 325 326 with open( 327 os.path.join( 328 bui.app.env.data_directory, 329 'ba_data', 330 'data', 331 'langdata.json', 332 ), 333 encoding='utf-8', 334 ) as infile: 335 lang_names_translated = json.loads(infile.read())[ 336 'lang_names_translated' 337 ] 338 except Exception: 339 logging.exception('Error reading lang data.') 340 lang_names_translated = {} 341 342 langs_translated = {} 343 for lang in languages: 344 langs_translated[lang] = lang_names_translated.get(lang, lang) 345 346 langs_full = {} 347 for lang in languages: 348 lang_translated = bui.Lstr(translate=('languages', lang)).evaluate() 349 if langs_translated[lang] == lang_translated: 350 langs_full[lang] = lang_translated 351 else: 352 langs_full[lang] = ( 353 langs_translated[lang] + ' (' + lang_translated + ')' 354 ) 355 356 self._language_popup = PopupMenu( 357 parent=self._subcontainer, 358 position=(210, v - 19), 359 width=150, 360 opening_call=bui.WeakCall(self._on_menu_open), 361 closing_call=bui.WeakCall(self._on_menu_close), 362 autoselect=False, 363 on_value_change_call=bui.WeakCall(self._on_menu_choice), 364 choices=['Auto'] + languages, 365 button_size=(250, 60), 366 choices_display=( 367 [ 368 bui.Lstr( 369 value=( 370 bui.Lstr(resource='autoText').evaluate() 371 + ' (' 372 + bui.Lstr( 373 translate=( 374 'languages', 375 bui.app.lang.default_language, 376 ) 377 ).evaluate() 378 + ')' 379 ) 380 ) 381 ] 382 + [bui.Lstr(value=langs_full[l]) for l in languages] 383 ), 384 current_choice=cur_lang, 385 ) 386 387 v -= self._spacing * 1.8 388 389 bui.textwidget( 390 parent=self._subcontainer, 391 position=(90, v + 10), 392 size=(0, 0), 393 text=bui.Lstr( 394 resource=f'{self._r}.helpTranslateText', 395 subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))], 396 ), 397 maxwidth=self._sub_width * 0.9, 398 max_height=55, 399 flatness=1.0, 400 scale=0.65, 401 color=(0.4, 0.9, 0.4, 0.8), 402 h_align='left', 403 v_align='center', 404 ) 405 v -= self._spacing * 1.9 406 this_button_width = 410 407 self._translation_editor_button = bui.buttonwidget( 408 parent=self._subcontainer, 409 position=(self._sub_width / 2 - this_button_width / 2, v - 24), 410 size=(this_button_width, 60), 411 label=bui.Lstr( 412 resource=f'{self._r}.translationEditorButtonText', 413 subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))], 414 ), 415 autoselect=True, 416 on_activate_call=bui.Call( 417 bui.open_url, 'https://legacy.ballistica.net/translate' 418 ), 419 ) 420 421 self._lang_status_text = bui.textwidget( 422 parent=self._subcontainer, 423 position=(self._sub_width * 0.5, v - 40), 424 size=(0, 0), 425 text='', 426 flatness=1.0, 427 scale=0.63, 428 h_align='center', 429 v_align='center', 430 maxwidth=400.0, 431 ) 432 self._update_lang_status() 433 v -= 50 434 435 lang_inform = plus.get_v1_account_misc_val('langInform', False) 436 437 self._language_inform_checkbox = cbw = bui.checkboxwidget( 438 parent=self._subcontainer, 439 position=(50, v - 50), 440 size=(self._sub_width - 100, 30), 441 autoselect=True, 442 maxwidth=430, 443 textcolor=(0.8, 0.8, 0.8), 444 value=lang_inform, 445 text=bui.Lstr(resource=f'{self._r}.translationInformMe'), 446 on_value_change_call=bui.WeakCall( 447 self._on_lang_inform_value_change 448 ), 449 ) 450 451 bui.widget( 452 edit=self._translation_editor_button, 453 down_widget=cbw, 454 up_widget=self._language_popup.get_button(), 455 ) 456 457 v -= self._spacing * 3.0 458 459 self._kick_idle_players_check_box = ConfigCheckBox( 460 parent=self._subcontainer, 461 position=(50, v), 462 size=(self._sub_width - 100, 30), 463 configkey='Kick Idle Players', 464 displayname=bui.Lstr(resource=f'{self._r}.kickIdlePlayersText'), 465 scale=1.0, 466 maxwidth=430, 467 ) 468 469 v -= 42 470 self._show_game_ping_check_box = ConfigCheckBox( 471 parent=self._subcontainer, 472 position=(50, v), 473 size=(self._sub_width - 100, 30), 474 configkey='Show Ping', 475 displayname=bui.Lstr(resource=f'{self._r}.showInGamePingText'), 476 scale=1.0, 477 maxwidth=430, 478 ) 479 480 v -= 42 481 self._show_demos_when_idle_check_box = ConfigCheckBox( 482 parent=self._subcontainer, 483 position=(50, v), 484 size=(self._sub_width - 100, 30), 485 configkey='Show Demos When Idle', 486 displayname=bui.Lstr(resource=f'{self._r}.showDemosWhenIdleText'), 487 scale=1.0, 488 maxwidth=430, 489 ) 490 491 v -= 42 492 self._show_deprecated_login_types_check_box = ConfigCheckBox( 493 parent=self._subcontainer, 494 position=(50, v), 495 size=(self._sub_width - 100, 30), 496 configkey='Show Deprecated Login Types', 497 displayname=bui.Lstr( 498 resource=f'{self._r}.showDeprecatedLoginTypesText' 499 ), 500 scale=1.0, 501 maxwidth=430, 502 ) 503 504 v -= 42 505 self._disable_camera_shake_check_box = ConfigCheckBox( 506 parent=self._subcontainer, 507 position=(50, v), 508 size=(self._sub_width - 100, 30), 509 configkey='Disable Camera Shake', 510 displayname=bui.Lstr(resource=f'{self._r}.disableCameraShakeText'), 511 scale=1.0, 512 maxwidth=430, 513 ) 514 515 self._disable_gyro_check_box: ConfigCheckBox | None = None 516 if self._show_disable_gyro: 517 v -= 42 518 self._disable_gyro_check_box = ConfigCheckBox( 519 parent=self._subcontainer, 520 position=(50, v), 521 size=(self._sub_width - 100, 30), 522 configkey='Disable Camera Gyro', 523 displayname=bui.Lstr( 524 resource=f'{self._r}.disableCameraGyroscopeMotionText' 525 ), 526 scale=1.0, 527 maxwidth=430, 528 ) 529 530 self._always_use_internal_keyboard_check_box: ConfigCheckBox | None 531 if self._show_always_use_internal_keyboard: 532 v -= 42 533 self._always_use_internal_keyboard_check_box = ConfigCheckBox( 534 parent=self._subcontainer, 535 position=(50, v), 536 size=(self._sub_width - 100, 30), 537 configkey='Always Use Internal Keyboard', 538 autoselect=True, 539 displayname=bui.Lstr( 540 resource=f'{self._r}.alwaysUseInternalKeyboardText' 541 ), 542 scale=1.0, 543 maxwidth=430, 544 ) 545 bui.textwidget( 546 parent=self._subcontainer, 547 position=(90, v - 10), 548 size=(0, 0), 549 text=bui.Lstr( 550 resource=( 551 f'{self._r}.alwaysUseInternalKeyboardDescriptionText' 552 ) 553 ), 554 maxwidth=400, 555 flatness=1.0, 556 scale=0.65, 557 color=(0.4, 0.9, 0.4, 0.8), 558 h_align='left', 559 v_align='center', 560 ) 561 v -= 20 562 else: 563 self._always_use_internal_keyboard_check_box = None 564 565 v -= self._spacing * 2.1 566 567 this_button_width = 410 568 self._modding_guide_button = bui.buttonwidget( 569 parent=self._subcontainer, 570 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 571 size=(this_button_width, 60), 572 autoselect=True, 573 label=bui.Lstr(resource=f'{self._r}.moddingGuideText'), 574 text_scale=1.0, 575 on_activate_call=bui.Call( 576 bui.open_url, 'https://ballistica.net/wiki/modding-guide' 577 ), 578 ) 579 580 v -= self._spacing * 2.0 581 582 self._dev_tools_button = bui.buttonwidget( 583 parent=self._subcontainer, 584 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 585 size=(this_button_width, 60), 586 autoselect=True, 587 label=bui.Lstr(resource=f'{self._r}.devToolsText'), 588 text_scale=1.0, 589 on_activate_call=self._on_dev_tools_button_press, 590 ) 591 592 if self._show_always_use_internal_keyboard: 593 assert self._always_use_internal_keyboard_check_box is not None 594 bui.widget( 595 edit=self._always_use_internal_keyboard_check_box.widget, 596 down_widget=self._modding_guide_button, 597 ) 598 bui.widget( 599 edit=self._modding_guide_button, 600 up_widget=self._always_use_internal_keyboard_check_box.widget, 601 ) 602 else: 603 # ew. 604 next_widget_up = ( 605 self._disable_gyro_check_box.widget 606 if self._disable_gyro_check_box is not None 607 else self._disable_camera_shake_check_box.widget 608 ) 609 bui.widget( 610 edit=self._modding_guide_button, 611 up_widget=next_widget_up, 612 ) 613 bui.widget( 614 edit=next_widget_up, 615 down_widget=self._modding_guide_button, 616 ) 617 618 v -= self._spacing * 2.0 619 620 self._show_user_mods_button = bui.buttonwidget( 621 parent=self._subcontainer, 622 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 623 size=(this_button_width, 60), 624 autoselect=True, 625 label=bui.Lstr(resource=f'{self._r}.showUserModsText'), 626 text_scale=1.0, 627 on_activate_call=show_user_scripts, 628 ) 629 630 v -= self._spacing * 2.0 631 632 self._plugins_button = bui.buttonwidget( 633 parent=self._subcontainer, 634 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 635 size=(this_button_width, 60), 636 autoselect=True, 637 label=bui.Lstr(resource='pluginsText'), 638 text_scale=1.0, 639 on_activate_call=self._on_plugins_button_press, 640 ) 641 642 v -= self._spacing * 0.6 643 644 self._vr_test_button: bui.Widget | None 645 if self._do_vr_test_button: 646 v -= self._extra_button_spacing 647 self._vr_test_button = bui.buttonwidget( 648 parent=self._subcontainer, 649 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 650 size=(this_button_width, 60), 651 autoselect=True, 652 label=bui.Lstr(resource=f'{self._r}.vrTestingText'), 653 text_scale=1.0, 654 on_activate_call=self._on_vr_test_press, 655 ) 656 else: 657 self._vr_test_button = None 658 659 self._net_test_button: bui.Widget | None 660 if self._do_net_test_button: 661 v -= self._extra_button_spacing 662 self._net_test_button = bui.buttonwidget( 663 parent=self._subcontainer, 664 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 665 size=(this_button_width, 60), 666 autoselect=True, 667 label=bui.Lstr(resource=f'{self._r}.netTestingText'), 668 text_scale=1.0, 669 on_activate_call=self._on_net_test_press, 670 ) 671 else: 672 self._net_test_button = None 673 674 v -= 70 675 self._benchmarks_button = bui.buttonwidget( 676 parent=self._subcontainer, 677 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 678 size=(this_button_width, 60), 679 autoselect=True, 680 label=bui.Lstr(resource=f'{self._r}.benchmarksText'), 681 text_scale=1.0, 682 on_activate_call=self._on_benchmark_press, 683 ) 684 685 v -= 100 686 self._send_info_button = bui.buttonwidget( 687 parent=self._subcontainer, 688 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 689 size=(this_button_width, 60), 690 autoselect=True, 691 label=bui.Lstr(resource=f'{self._r}.sendInfoText'), 692 text_scale=1.0, 693 on_activate_call=self._on_send_info_press, 694 ) 695 696 for child in self._subcontainer.get_children(): 697 bui.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20) 698 699 pbtn = bui.get_special_widget('squad_button') 700 bui.widget(edit=self._scrollwidget, right_widget=pbtn) 701 if self._back_button is None: 702 bui.widget( 703 edit=self._scrollwidget, 704 left_widget=bui.get_special_widget('back_button'), 705 ) 706 707 self._restore_state() 708 709 def _show_restart_needed(self, value: Any) -> None: 710 del value # Unused. 711 bui.screenmessage( 712 bui.Lstr(resource=f'{self._r}.mustRestartText'), color=(1, 1, 0) 713 ) 714 715 def _on_lang_inform_value_change(self, val: bool) -> None: 716 plus = bui.app.plus 717 assert plus is not None 718 plus.add_v1_account_transaction( 719 {'type': 'SET_MISC_VAL', 'name': 'langInform', 'value': val} 720 ) 721 plus.run_v1_account_transactions() 722 723 def _on_vr_test_press(self) -> None: 724 from bauiv1lib.settings.vrtesting import VRTestingWindow 725 726 # no-op if we're not in control. 727 if not self.main_window_has_control(): 728 return 729 730 self.main_window_replace(VRTestingWindow(transition='in_right')) 731 732 def _on_net_test_press(self) -> None: 733 from bauiv1lib.settings.nettesting import NetTestingWindow 734 735 # no-op if we're not in control. 736 if not self.main_window_has_control(): 737 return 738 739 self.main_window_replace(NetTestingWindow(transition='in_right')) 740 741 def _on_friend_promo_code_press(self) -> None: 742 from bauiv1lib import appinvite 743 from bauiv1lib import account 744 745 plus = bui.app.plus 746 assert plus is not None 747 748 if plus.get_v1_account_state() != 'signed_in': 749 account.show_sign_in_prompt() 750 return 751 appinvite.handle_app_invites_press() 752 753 def _on_plugins_button_press(self) -> None: 754 from bauiv1lib.settings.plugins import PluginWindow 755 756 # no-op if we're not in control. 757 if not self.main_window_has_control(): 758 return 759 760 self.main_window_replace( 761 PluginWindow(origin_widget=self._plugins_button) 762 ) 763 764 def _on_dev_tools_button_press(self) -> None: 765 # pylint: disable=cyclic-import 766 from bauiv1lib.settings.devtools import DevToolsWindow 767 768 # no-op if we're not in control. 769 if not self.main_window_has_control(): 770 return 771 772 self.main_window_replace( 773 DevToolsWindow(origin_widget=self._dev_tools_button) 774 ) 775 776 def _on_send_info_press(self) -> None: 777 from bauiv1lib.sendinfo import SendInfoWindow 778 779 # no-op if we're not in control. 780 if not self.main_window_has_control(): 781 return 782 783 self.main_window_replace( 784 SendInfoWindow(origin_widget=self._send_info_button) 785 ) 786 787 def _on_benchmark_press(self) -> None: 788 from bauiv1lib.settings.benchmarks import BenchmarksAndStressTestsWindow 789 790 # no-op if we're not in control. 791 if not self.main_window_has_control(): 792 return 793 794 self.main_window_replace( 795 BenchmarksAndStressTestsWindow(transition='in_right') 796 ) 797 798 def _save_state(self) -> None: 799 # pylint: disable=too-many-branches 800 # pylint: disable=too-many-statements 801 try: 802 sel = self._root_widget.get_selected_child() 803 if sel == self._scrollwidget: 804 sel = self._subcontainer.get_selected_child() 805 if sel == self._vr_test_button: 806 sel_name = 'VRTest' 807 elif sel == self._net_test_button: 808 sel_name = 'NetTest' 809 elif sel == self._send_info_button: 810 sel_name = 'SendInfo' 811 elif sel == self._benchmarks_button: 812 sel_name = 'Benchmarks' 813 elif sel == self._kick_idle_players_check_box.widget: 814 sel_name = 'KickIdlePlayers' 815 elif sel == self._show_demos_when_idle_check_box.widget: 816 sel_name = 'ShowDemosWhenIdle' 817 elif sel == self._show_deprecated_login_types_check_box.widget: 818 sel_name = 'ShowDeprecatedLoginTypes' 819 elif sel == self._show_game_ping_check_box.widget: 820 sel_name = 'ShowPing' 821 elif sel == self._disable_camera_shake_check_box.widget: 822 sel_name = 'DisableCameraShake' 823 elif ( 824 self._always_use_internal_keyboard_check_box is not None 825 and sel 826 == self._always_use_internal_keyboard_check_box.widget 827 ): 828 sel_name = 'AlwaysUseInternalKeyboard' 829 elif ( 830 self._disable_gyro_check_box is not None 831 and sel == self._disable_gyro_check_box.widget 832 ): 833 sel_name = 'DisableGyro' 834 elif ( 835 self._language_popup is not None 836 and sel == self._language_popup.get_button() 837 ): 838 sel_name = 'Languages' 839 elif sel == self._translation_editor_button: 840 sel_name = 'TranslationEditor' 841 elif sel == self._show_user_mods_button: 842 sel_name = 'ShowUserMods' 843 elif sel == self._plugins_button: 844 sel_name = 'Plugins' 845 elif sel == self._dev_tools_button: 846 sel_name = 'DevTools' 847 elif sel == self._modding_guide_button: 848 sel_name = 'ModdingGuide' 849 elif sel == self._language_inform_checkbox: 850 sel_name = 'LangInform' 851 else: 852 raise ValueError(f'unrecognized selection \'{sel}\'') 853 elif sel == self._back_button: 854 sel_name = 'Back' 855 else: 856 raise ValueError(f'unrecognized selection \'{sel}\'') 857 assert bui.app.classic is not None 858 bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name} 859 860 except Exception: 861 logging.exception('Error saving state for %s.', self) 862 863 def _restore_state(self) -> None: 864 # pylint: disable=too-many-branches 865 try: 866 assert bui.app.classic is not None 867 sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get( 868 'sel_name' 869 ) 870 if sel_name == 'Back': 871 sel = self._back_button 872 else: 873 bui.containerwidget( 874 edit=self._root_widget, selected_child=self._scrollwidget 875 ) 876 if sel_name == 'VRTest': 877 sel = self._vr_test_button 878 elif sel_name == 'NetTest': 879 sel = self._net_test_button 880 elif sel_name == 'SendInfo': 881 sel = self._send_info_button 882 elif sel_name == 'Benchmarks': 883 sel = self._benchmarks_button 884 elif sel_name == 'KickIdlePlayers': 885 sel = self._kick_idle_players_check_box.widget 886 elif sel_name == 'ShowDemosWhenIdle': 887 sel = self._show_demos_when_idle_check_box.widget 888 elif sel_name == 'ShowDeprecatedLoginTypes': 889 sel = self._show_deprecated_login_types_check_box.widget 890 elif sel_name == 'ShowPing': 891 sel = self._show_game_ping_check_box.widget 892 elif sel_name == 'DisableCameraShake': 893 sel = self._disable_camera_shake_check_box.widget 894 elif ( 895 sel_name == 'AlwaysUseInternalKeyboard' 896 and self._always_use_internal_keyboard_check_box is not None 897 ): 898 sel = self._always_use_internal_keyboard_check_box.widget 899 elif ( 900 sel_name == 'DisableGyro' 901 and self._disable_gyro_check_box is not None 902 ): 903 sel = self._disable_gyro_check_box.widget 904 elif ( 905 sel_name == 'Languages' and self._language_popup is not None 906 ): 907 sel = self._language_popup.get_button() 908 elif sel_name == 'TranslationEditor': 909 sel = self._translation_editor_button 910 elif sel_name == 'ShowUserMods': 911 sel = self._show_user_mods_button 912 elif sel_name == 'Plugins': 913 sel = self._plugins_button 914 elif sel_name == 'DevTools': 915 sel = self._dev_tools_button 916 elif sel_name == 'ModdingGuide': 917 sel = self._modding_guide_button 918 elif sel_name == 'LangInform': 919 sel = self._language_inform_checkbox 920 else: 921 sel = None 922 if sel is not None: 923 bui.containerwidget( 924 edit=self._subcontainer, 925 selected_child=sel, 926 visible_child=sel, 927 ) 928 except Exception: 929 logging.exception('Error restoring state for %s.', self) 930 931 def _on_menu_open(self) -> None: 932 self._menu_open = True 933 934 def _on_menu_close(self) -> None: 935 self._menu_open = False 936 937 def _on_menu_choice(self, choice: str) -> None: 938 bui.app.lang.setlanguage(None if choice == 'Auto' else choice) 939 self._save_state() 940 bui.apptimer(0.1, bui.WeakCall(self._rebuild)) 941 942 def _completed_langs_cb(self, results: dict[str, Any] | None) -> None: 943 if results is not None and results['langs'] is not None: 944 self._complete_langs_list = results['langs'] 945 self._complete_langs_error = False 946 else: 947 self._complete_langs_list = None 948 self._complete_langs_error = True 949 bui.apptimer(0.001, bui.WeakCall(self._update_lang_status))
class
AdvancedSettingsWindow(bauiv1._uitypes.MainWindow):
19class AdvancedSettingsWindow(bui.MainWindow): 20 """Window for editing advanced app settings.""" 21 22 def __init__( 23 self, 24 transition: str | None = 'in_right', 25 origin_widget: bui.Widget | None = None, 26 ): 27 # pylint: disable=too-many-statements 28 29 if bui.app.classic is None: 30 raise RuntimeError('This requires classic support.') 31 32 # Preload some modules we use in a background thread so we won't 33 # have a visual hitch when the user taps them. 34 bui.app.threadpool_submit_no_wait(self._preload_modules) 35 36 app = bui.app 37 assert app.classic is not None 38 39 uiscale = bui.app.ui_v1.uiscale 40 self._width = 1030.0 if uiscale is bui.UIScale.SMALL else 670.0 41 x_inset = 150 if uiscale is bui.UIScale.SMALL else 0 42 self._height = ( 43 390.0 44 if uiscale is bui.UIScale.SMALL 45 else 450.0 if uiscale is bui.UIScale.MEDIUM else 520.0 46 ) 47 self._lang_status_text: bui.Widget | None = None 48 49 self._spacing = 32 50 self._menu_open = False 51 top_extra = 10 if uiscale is bui.UIScale.SMALL else 0 52 53 super().__init__( 54 root_widget=bui.containerwidget( 55 size=(self._width, self._height + top_extra), 56 toolbar_visibility=( 57 'menu_minimal' 58 if uiscale is bui.UIScale.SMALL 59 else 'menu_full' 60 ), 61 scale=( 62 2.04 63 if uiscale is bui.UIScale.SMALL 64 else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0 65 ), 66 stack_offset=( 67 (0, 10) if uiscale is bui.UIScale.SMALL else (0, 0) 68 ), 69 ), 70 transition=transition, 71 origin_widget=origin_widget, 72 ) 73 74 self._prev_lang = '' 75 self._prev_lang_list: list[str] = [] 76 self._complete_langs_list: list | None = None 77 self._complete_langs_error = False 78 self._language_popup: PopupMenu | None = None 79 80 # In vr-mode, the internal keyboard is currently the *only* option, 81 # so no need to show this. 82 self._show_always_use_internal_keyboard = not app.env.vr 83 84 self._scroll_width = self._width - (100 + 2 * x_inset) 85 self._scroll_height = self._height - ( 86 125.0 if uiscale is bui.UIScale.SMALL else 115.0 87 ) 88 self._sub_width = self._scroll_width * 0.95 89 self._sub_height = 870.0 90 91 if self._show_always_use_internal_keyboard: 92 self._sub_height += 62 93 94 self._show_disable_gyro = app.classic.platform in {'ios', 'android'} 95 if self._show_disable_gyro: 96 self._sub_height += 42 97 98 self._do_vr_test_button = app.env.vr 99 self._do_net_test_button = True 100 self._extra_button_spacing = self._spacing * 2.5 101 102 if self._do_vr_test_button: 103 self._sub_height += self._extra_button_spacing 104 if self._do_net_test_button: 105 self._sub_height += self._extra_button_spacing 106 self._sub_height += self._spacing * 2.0 # plugins 107 self._sub_height += self._spacing * 2.0 # dev tools 108 109 self._r = 'settingsWindowAdvanced' 110 111 if uiscale is bui.UIScale.SMALL: 112 bui.containerwidget( 113 edit=self._root_widget, on_cancel_call=self.main_window_back 114 ) 115 self._back_button = None 116 else: 117 self._back_button = bui.buttonwidget( 118 parent=self._root_widget, 119 position=(53 + x_inset, self._height - 60), 120 size=(140, 60), 121 scale=0.8, 122 autoselect=True, 123 label=bui.Lstr(resource='backText'), 124 button_type='back', 125 on_activate_call=self.main_window_back, 126 ) 127 bui.containerwidget( 128 edit=self._root_widget, cancel_button=self._back_button 129 ) 130 131 self._title_text = bui.textwidget( 132 parent=self._root_widget, 133 position=( 134 self._width * 0.5, 135 self._height - (61 if uiscale is bui.UIScale.SMALL else 40), 136 ), 137 size=(0, 0), 138 scale=0.85 if uiscale is bui.UIScale.SMALL else 1.0, 139 text=bui.Lstr(resource=f'{self._r}.titleText'), 140 color=app.ui_v1.title_color, 141 h_align='center', 142 v_align='center', 143 ) 144 145 if self._back_button is not None: 146 bui.buttonwidget( 147 edit=self._back_button, 148 button_type='backSmall', 149 size=(60, 60), 150 label=bui.charstr(bui.SpecialChar.BACK), 151 ) 152 153 self._scrollwidget = bui.scrollwidget( 154 parent=self._root_widget, 155 position=(50 + x_inset, 50), 156 simple_culling_v=20.0, 157 highlight=False, 158 size=(self._scroll_width, self._scroll_height), 159 selection_loops_to_parent=True, 160 ) 161 bui.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) 162 self._subcontainer = bui.containerwidget( 163 parent=self._scrollwidget, 164 size=(self._sub_width, self._sub_height), 165 background=False, 166 selection_loops_to_parent=True, 167 ) 168 169 self._rebuild() 170 171 # Rebuild periodically to pick up language changes/additions/etc. 172 self._rebuild_timer = bui.AppTimer( 173 1.0, bui.WeakCall(self._rebuild), repeat=True 174 ) 175 176 # Fetch the list of completed languages. 177 bui.app.classic.master_server_v1_get( 178 'bsLangGetCompleted', 179 {'b': app.env.engine_build_number}, 180 callback=bui.WeakCall(self._completed_langs_cb), 181 ) 182 183 @override 184 def get_main_window_state(self) -> bui.MainWindowState: 185 # Support recreating our window for back/refresh purposes. 186 cls = type(self) 187 return bui.BasicMainWindowState( 188 create_call=lambda transition, origin_widget: cls( 189 transition=transition, origin_widget=origin_widget 190 ) 191 ) 192 193 @override 194 def on_main_window_close(self) -> None: 195 self._save_state() 196 197 @staticmethod 198 def _preload_modules() -> None: 199 """Preload stuff in bg thread to avoid hitches in logic thread""" 200 from babase import modutils as _unused2 201 from bauiv1lib import config as _unused1 202 from bauiv1lib.settings import vrtesting as _unused3 203 from bauiv1lib.settings import nettesting as _unused4 204 from bauiv1lib import appinvite as _unused5 205 from bauiv1lib import account as _unused6 206 from bauiv1lib import sendinfo as _unused7 207 from bauiv1lib.settings import benchmarks as _unused8 208 from bauiv1lib.settings import plugins as _unused9 209 from bauiv1lib.settings import devtools as _unused10 210 211 def _update_lang_status(self) -> None: 212 if self._complete_langs_list is not None: 213 up_to_date = bui.app.lang.language in self._complete_langs_list 214 bui.textwidget( 215 edit=self._lang_status_text, 216 text=( 217 '' 218 if bui.app.lang.language == 'Test' 219 else ( 220 bui.Lstr( 221 resource=f'{self._r}.translationNoUpdateNeededText' 222 ) 223 if up_to_date 224 else bui.Lstr( 225 resource=f'{self._r}.translationUpdateNeededText' 226 ) 227 ) 228 ), 229 color=( 230 (0.2, 1.0, 0.2, 0.8) if up_to_date else (1.0, 0.2, 0.2, 0.8) 231 ), 232 ) 233 else: 234 bui.textwidget( 235 edit=self._lang_status_text, 236 text=( 237 bui.Lstr(resource=f'{self._r}.translationFetchErrorText') 238 if self._complete_langs_error 239 else bui.Lstr( 240 resource=f'{self._r}.translationFetchingStatusText' 241 ) 242 ), 243 color=( 244 (1.0, 0.5, 0.2) 245 if self._complete_langs_error 246 else (0.7, 0.7, 0.7) 247 ), 248 ) 249 250 def _rebuild(self) -> None: 251 # pylint: disable=too-many-statements 252 # pylint: disable=too-many-branches 253 # pylint: disable=too-many-locals 254 255 from bauiv1lib.config import ConfigCheckBox 256 from babase.modutils import show_user_scripts 257 258 plus = bui.app.plus 259 assert plus is not None 260 261 available_languages = bui.app.lang.available_languages 262 263 # Don't rebuild if the menu is open or if our language and 264 # language-list hasn't changed. 265 266 # NOTE - although we now support widgets updating their own 267 # translations, we still change the label formatting on the language 268 # menu based on the language so still need this. ...however we could 269 # make this more limited to it only rebuilds that one menu instead 270 # of everything. 271 if self._menu_open or ( 272 self._prev_lang == bui.app.config.get('Lang', None) 273 and self._prev_lang_list == available_languages 274 ): 275 return 276 self._prev_lang = bui.app.config.get('Lang', None) 277 self._prev_lang_list = available_languages 278 279 # Clear out our sub-container. 280 children = self._subcontainer.get_children() 281 for child in children: 282 child.delete() 283 284 v = self._sub_height - 35 285 286 v -= self._spacing * 1.2 287 288 # Update our existing back button and title. 289 if self._back_button is not None: 290 bui.buttonwidget( 291 edit=self._back_button, label=bui.Lstr(resource='backText') 292 ) 293 bui.buttonwidget( 294 edit=self._back_button, label=bui.charstr(bui.SpecialChar.BACK) 295 ) 296 297 bui.textwidget( 298 edit=self._title_text, 299 text=bui.Lstr(resource=f'{self._r}.titleText'), 300 ) 301 302 this_button_width = 410 303 304 assert bui.app.classic is not None 305 bui.textwidget( 306 parent=self._subcontainer, 307 position=(70, v + 10), 308 size=(0, 0), 309 text=bui.Lstr(resource=f'{self._r}.languageText'), 310 maxwidth=150, 311 scale=1.2, 312 color=bui.app.ui_v1.title_color, 313 h_align='left', 314 v_align='center', 315 ) 316 317 languages = bui.app.lang.available_languages 318 cur_lang = bui.app.config.get('Lang', None) 319 if cur_lang is None: 320 cur_lang = 'Auto' 321 322 # We have a special dict of language names in that language 323 # so we don't have to go digging through each full language. 324 try: 325 import json 326 327 with open( 328 os.path.join( 329 bui.app.env.data_directory, 330 'ba_data', 331 'data', 332 'langdata.json', 333 ), 334 encoding='utf-8', 335 ) as infile: 336 lang_names_translated = json.loads(infile.read())[ 337 'lang_names_translated' 338 ] 339 except Exception: 340 logging.exception('Error reading lang data.') 341 lang_names_translated = {} 342 343 langs_translated = {} 344 for lang in languages: 345 langs_translated[lang] = lang_names_translated.get(lang, lang) 346 347 langs_full = {} 348 for lang in languages: 349 lang_translated = bui.Lstr(translate=('languages', lang)).evaluate() 350 if langs_translated[lang] == lang_translated: 351 langs_full[lang] = lang_translated 352 else: 353 langs_full[lang] = ( 354 langs_translated[lang] + ' (' + lang_translated + ')' 355 ) 356 357 self._language_popup = PopupMenu( 358 parent=self._subcontainer, 359 position=(210, v - 19), 360 width=150, 361 opening_call=bui.WeakCall(self._on_menu_open), 362 closing_call=bui.WeakCall(self._on_menu_close), 363 autoselect=False, 364 on_value_change_call=bui.WeakCall(self._on_menu_choice), 365 choices=['Auto'] + languages, 366 button_size=(250, 60), 367 choices_display=( 368 [ 369 bui.Lstr( 370 value=( 371 bui.Lstr(resource='autoText').evaluate() 372 + ' (' 373 + bui.Lstr( 374 translate=( 375 'languages', 376 bui.app.lang.default_language, 377 ) 378 ).evaluate() 379 + ')' 380 ) 381 ) 382 ] 383 + [bui.Lstr(value=langs_full[l]) for l in languages] 384 ), 385 current_choice=cur_lang, 386 ) 387 388 v -= self._spacing * 1.8 389 390 bui.textwidget( 391 parent=self._subcontainer, 392 position=(90, v + 10), 393 size=(0, 0), 394 text=bui.Lstr( 395 resource=f'{self._r}.helpTranslateText', 396 subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))], 397 ), 398 maxwidth=self._sub_width * 0.9, 399 max_height=55, 400 flatness=1.0, 401 scale=0.65, 402 color=(0.4, 0.9, 0.4, 0.8), 403 h_align='left', 404 v_align='center', 405 ) 406 v -= self._spacing * 1.9 407 this_button_width = 410 408 self._translation_editor_button = bui.buttonwidget( 409 parent=self._subcontainer, 410 position=(self._sub_width / 2 - this_button_width / 2, v - 24), 411 size=(this_button_width, 60), 412 label=bui.Lstr( 413 resource=f'{self._r}.translationEditorButtonText', 414 subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))], 415 ), 416 autoselect=True, 417 on_activate_call=bui.Call( 418 bui.open_url, 'https://legacy.ballistica.net/translate' 419 ), 420 ) 421 422 self._lang_status_text = bui.textwidget( 423 parent=self._subcontainer, 424 position=(self._sub_width * 0.5, v - 40), 425 size=(0, 0), 426 text='', 427 flatness=1.0, 428 scale=0.63, 429 h_align='center', 430 v_align='center', 431 maxwidth=400.0, 432 ) 433 self._update_lang_status() 434 v -= 50 435 436 lang_inform = plus.get_v1_account_misc_val('langInform', False) 437 438 self._language_inform_checkbox = cbw = bui.checkboxwidget( 439 parent=self._subcontainer, 440 position=(50, v - 50), 441 size=(self._sub_width - 100, 30), 442 autoselect=True, 443 maxwidth=430, 444 textcolor=(0.8, 0.8, 0.8), 445 value=lang_inform, 446 text=bui.Lstr(resource=f'{self._r}.translationInformMe'), 447 on_value_change_call=bui.WeakCall( 448 self._on_lang_inform_value_change 449 ), 450 ) 451 452 bui.widget( 453 edit=self._translation_editor_button, 454 down_widget=cbw, 455 up_widget=self._language_popup.get_button(), 456 ) 457 458 v -= self._spacing * 3.0 459 460 self._kick_idle_players_check_box = ConfigCheckBox( 461 parent=self._subcontainer, 462 position=(50, v), 463 size=(self._sub_width - 100, 30), 464 configkey='Kick Idle Players', 465 displayname=bui.Lstr(resource=f'{self._r}.kickIdlePlayersText'), 466 scale=1.0, 467 maxwidth=430, 468 ) 469 470 v -= 42 471 self._show_game_ping_check_box = ConfigCheckBox( 472 parent=self._subcontainer, 473 position=(50, v), 474 size=(self._sub_width - 100, 30), 475 configkey='Show Ping', 476 displayname=bui.Lstr(resource=f'{self._r}.showInGamePingText'), 477 scale=1.0, 478 maxwidth=430, 479 ) 480 481 v -= 42 482 self._show_demos_when_idle_check_box = ConfigCheckBox( 483 parent=self._subcontainer, 484 position=(50, v), 485 size=(self._sub_width - 100, 30), 486 configkey='Show Demos When Idle', 487 displayname=bui.Lstr(resource=f'{self._r}.showDemosWhenIdleText'), 488 scale=1.0, 489 maxwidth=430, 490 ) 491 492 v -= 42 493 self._show_deprecated_login_types_check_box = ConfigCheckBox( 494 parent=self._subcontainer, 495 position=(50, v), 496 size=(self._sub_width - 100, 30), 497 configkey='Show Deprecated Login Types', 498 displayname=bui.Lstr( 499 resource=f'{self._r}.showDeprecatedLoginTypesText' 500 ), 501 scale=1.0, 502 maxwidth=430, 503 ) 504 505 v -= 42 506 self._disable_camera_shake_check_box = ConfigCheckBox( 507 parent=self._subcontainer, 508 position=(50, v), 509 size=(self._sub_width - 100, 30), 510 configkey='Disable Camera Shake', 511 displayname=bui.Lstr(resource=f'{self._r}.disableCameraShakeText'), 512 scale=1.0, 513 maxwidth=430, 514 ) 515 516 self._disable_gyro_check_box: ConfigCheckBox | None = None 517 if self._show_disable_gyro: 518 v -= 42 519 self._disable_gyro_check_box = ConfigCheckBox( 520 parent=self._subcontainer, 521 position=(50, v), 522 size=(self._sub_width - 100, 30), 523 configkey='Disable Camera Gyro', 524 displayname=bui.Lstr( 525 resource=f'{self._r}.disableCameraGyroscopeMotionText' 526 ), 527 scale=1.0, 528 maxwidth=430, 529 ) 530 531 self._always_use_internal_keyboard_check_box: ConfigCheckBox | None 532 if self._show_always_use_internal_keyboard: 533 v -= 42 534 self._always_use_internal_keyboard_check_box = ConfigCheckBox( 535 parent=self._subcontainer, 536 position=(50, v), 537 size=(self._sub_width - 100, 30), 538 configkey='Always Use Internal Keyboard', 539 autoselect=True, 540 displayname=bui.Lstr( 541 resource=f'{self._r}.alwaysUseInternalKeyboardText' 542 ), 543 scale=1.0, 544 maxwidth=430, 545 ) 546 bui.textwidget( 547 parent=self._subcontainer, 548 position=(90, v - 10), 549 size=(0, 0), 550 text=bui.Lstr( 551 resource=( 552 f'{self._r}.alwaysUseInternalKeyboardDescriptionText' 553 ) 554 ), 555 maxwidth=400, 556 flatness=1.0, 557 scale=0.65, 558 color=(0.4, 0.9, 0.4, 0.8), 559 h_align='left', 560 v_align='center', 561 ) 562 v -= 20 563 else: 564 self._always_use_internal_keyboard_check_box = None 565 566 v -= self._spacing * 2.1 567 568 this_button_width = 410 569 self._modding_guide_button = bui.buttonwidget( 570 parent=self._subcontainer, 571 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 572 size=(this_button_width, 60), 573 autoselect=True, 574 label=bui.Lstr(resource=f'{self._r}.moddingGuideText'), 575 text_scale=1.0, 576 on_activate_call=bui.Call( 577 bui.open_url, 'https://ballistica.net/wiki/modding-guide' 578 ), 579 ) 580 581 v -= self._spacing * 2.0 582 583 self._dev_tools_button = bui.buttonwidget( 584 parent=self._subcontainer, 585 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 586 size=(this_button_width, 60), 587 autoselect=True, 588 label=bui.Lstr(resource=f'{self._r}.devToolsText'), 589 text_scale=1.0, 590 on_activate_call=self._on_dev_tools_button_press, 591 ) 592 593 if self._show_always_use_internal_keyboard: 594 assert self._always_use_internal_keyboard_check_box is not None 595 bui.widget( 596 edit=self._always_use_internal_keyboard_check_box.widget, 597 down_widget=self._modding_guide_button, 598 ) 599 bui.widget( 600 edit=self._modding_guide_button, 601 up_widget=self._always_use_internal_keyboard_check_box.widget, 602 ) 603 else: 604 # ew. 605 next_widget_up = ( 606 self._disable_gyro_check_box.widget 607 if self._disable_gyro_check_box is not None 608 else self._disable_camera_shake_check_box.widget 609 ) 610 bui.widget( 611 edit=self._modding_guide_button, 612 up_widget=next_widget_up, 613 ) 614 bui.widget( 615 edit=next_widget_up, 616 down_widget=self._modding_guide_button, 617 ) 618 619 v -= self._spacing * 2.0 620 621 self._show_user_mods_button = bui.buttonwidget( 622 parent=self._subcontainer, 623 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 624 size=(this_button_width, 60), 625 autoselect=True, 626 label=bui.Lstr(resource=f'{self._r}.showUserModsText'), 627 text_scale=1.0, 628 on_activate_call=show_user_scripts, 629 ) 630 631 v -= self._spacing * 2.0 632 633 self._plugins_button = bui.buttonwidget( 634 parent=self._subcontainer, 635 position=(self._sub_width / 2 - this_button_width / 2, v - 10), 636 size=(this_button_width, 60), 637 autoselect=True, 638 label=bui.Lstr(resource='pluginsText'), 639 text_scale=1.0, 640 on_activate_call=self._on_plugins_button_press, 641 ) 642 643 v -= self._spacing * 0.6 644 645 self._vr_test_button: bui.Widget | None 646 if self._do_vr_test_button: 647 v -= self._extra_button_spacing 648 self._vr_test_button = bui.buttonwidget( 649 parent=self._subcontainer, 650 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 651 size=(this_button_width, 60), 652 autoselect=True, 653 label=bui.Lstr(resource=f'{self._r}.vrTestingText'), 654 text_scale=1.0, 655 on_activate_call=self._on_vr_test_press, 656 ) 657 else: 658 self._vr_test_button = None 659 660 self._net_test_button: bui.Widget | None 661 if self._do_net_test_button: 662 v -= self._extra_button_spacing 663 self._net_test_button = bui.buttonwidget( 664 parent=self._subcontainer, 665 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 666 size=(this_button_width, 60), 667 autoselect=True, 668 label=bui.Lstr(resource=f'{self._r}.netTestingText'), 669 text_scale=1.0, 670 on_activate_call=self._on_net_test_press, 671 ) 672 else: 673 self._net_test_button = None 674 675 v -= 70 676 self._benchmarks_button = bui.buttonwidget( 677 parent=self._subcontainer, 678 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 679 size=(this_button_width, 60), 680 autoselect=True, 681 label=bui.Lstr(resource=f'{self._r}.benchmarksText'), 682 text_scale=1.0, 683 on_activate_call=self._on_benchmark_press, 684 ) 685 686 v -= 100 687 self._send_info_button = bui.buttonwidget( 688 parent=self._subcontainer, 689 position=(self._sub_width / 2 - this_button_width / 2, v - 14), 690 size=(this_button_width, 60), 691 autoselect=True, 692 label=bui.Lstr(resource=f'{self._r}.sendInfoText'), 693 text_scale=1.0, 694 on_activate_call=self._on_send_info_press, 695 ) 696 697 for child in self._subcontainer.get_children(): 698 bui.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20) 699 700 pbtn = bui.get_special_widget('squad_button') 701 bui.widget(edit=self._scrollwidget, right_widget=pbtn) 702 if self._back_button is None: 703 bui.widget( 704 edit=self._scrollwidget, 705 left_widget=bui.get_special_widget('back_button'), 706 ) 707 708 self._restore_state() 709 710 def _show_restart_needed(self, value: Any) -> None: 711 del value # Unused. 712 bui.screenmessage( 713 bui.Lstr(resource=f'{self._r}.mustRestartText'), color=(1, 1, 0) 714 ) 715 716 def _on_lang_inform_value_change(self, val: bool) -> None: 717 plus = bui.app.plus 718 assert plus is not None 719 plus.add_v1_account_transaction( 720 {'type': 'SET_MISC_VAL', 'name': 'langInform', 'value': val} 721 ) 722 plus.run_v1_account_transactions() 723 724 def _on_vr_test_press(self) -> None: 725 from bauiv1lib.settings.vrtesting import VRTestingWindow 726 727 # no-op if we're not in control. 728 if not self.main_window_has_control(): 729 return 730 731 self.main_window_replace(VRTestingWindow(transition='in_right')) 732 733 def _on_net_test_press(self) -> None: 734 from bauiv1lib.settings.nettesting import NetTestingWindow 735 736 # no-op if we're not in control. 737 if not self.main_window_has_control(): 738 return 739 740 self.main_window_replace(NetTestingWindow(transition='in_right')) 741 742 def _on_friend_promo_code_press(self) -> None: 743 from bauiv1lib import appinvite 744 from bauiv1lib import account 745 746 plus = bui.app.plus 747 assert plus is not None 748 749 if plus.get_v1_account_state() != 'signed_in': 750 account.show_sign_in_prompt() 751 return 752 appinvite.handle_app_invites_press() 753 754 def _on_plugins_button_press(self) -> None: 755 from bauiv1lib.settings.plugins import PluginWindow 756 757 # no-op if we're not in control. 758 if not self.main_window_has_control(): 759 return 760 761 self.main_window_replace( 762 PluginWindow(origin_widget=self._plugins_button) 763 ) 764 765 def _on_dev_tools_button_press(self) -> None: 766 # pylint: disable=cyclic-import 767 from bauiv1lib.settings.devtools import DevToolsWindow 768 769 # no-op if we're not in control. 770 if not self.main_window_has_control(): 771 return 772 773 self.main_window_replace( 774 DevToolsWindow(origin_widget=self._dev_tools_button) 775 ) 776 777 def _on_send_info_press(self) -> None: 778 from bauiv1lib.sendinfo import SendInfoWindow 779 780 # no-op if we're not in control. 781 if not self.main_window_has_control(): 782 return 783 784 self.main_window_replace( 785 SendInfoWindow(origin_widget=self._send_info_button) 786 ) 787 788 def _on_benchmark_press(self) -> None: 789 from bauiv1lib.settings.benchmarks import BenchmarksAndStressTestsWindow 790 791 # no-op if we're not in control. 792 if not self.main_window_has_control(): 793 return 794 795 self.main_window_replace( 796 BenchmarksAndStressTestsWindow(transition='in_right') 797 ) 798 799 def _save_state(self) -> None: 800 # pylint: disable=too-many-branches 801 # pylint: disable=too-many-statements 802 try: 803 sel = self._root_widget.get_selected_child() 804 if sel == self._scrollwidget: 805 sel = self._subcontainer.get_selected_child() 806 if sel == self._vr_test_button: 807 sel_name = 'VRTest' 808 elif sel == self._net_test_button: 809 sel_name = 'NetTest' 810 elif sel == self._send_info_button: 811 sel_name = 'SendInfo' 812 elif sel == self._benchmarks_button: 813 sel_name = 'Benchmarks' 814 elif sel == self._kick_idle_players_check_box.widget: 815 sel_name = 'KickIdlePlayers' 816 elif sel == self._show_demos_when_idle_check_box.widget: 817 sel_name = 'ShowDemosWhenIdle' 818 elif sel == self._show_deprecated_login_types_check_box.widget: 819 sel_name = 'ShowDeprecatedLoginTypes' 820 elif sel == self._show_game_ping_check_box.widget: 821 sel_name = 'ShowPing' 822 elif sel == self._disable_camera_shake_check_box.widget: 823 sel_name = 'DisableCameraShake' 824 elif ( 825 self._always_use_internal_keyboard_check_box is not None 826 and sel 827 == self._always_use_internal_keyboard_check_box.widget 828 ): 829 sel_name = 'AlwaysUseInternalKeyboard' 830 elif ( 831 self._disable_gyro_check_box is not None 832 and sel == self._disable_gyro_check_box.widget 833 ): 834 sel_name = 'DisableGyro' 835 elif ( 836 self._language_popup is not None 837 and sel == self._language_popup.get_button() 838 ): 839 sel_name = 'Languages' 840 elif sel == self._translation_editor_button: 841 sel_name = 'TranslationEditor' 842 elif sel == self._show_user_mods_button: 843 sel_name = 'ShowUserMods' 844 elif sel == self._plugins_button: 845 sel_name = 'Plugins' 846 elif sel == self._dev_tools_button: 847 sel_name = 'DevTools' 848 elif sel == self._modding_guide_button: 849 sel_name = 'ModdingGuide' 850 elif sel == self._language_inform_checkbox: 851 sel_name = 'LangInform' 852 else: 853 raise ValueError(f'unrecognized selection \'{sel}\'') 854 elif sel == self._back_button: 855 sel_name = 'Back' 856 else: 857 raise ValueError(f'unrecognized selection \'{sel}\'') 858 assert bui.app.classic is not None 859 bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name} 860 861 except Exception: 862 logging.exception('Error saving state for %s.', self) 863 864 def _restore_state(self) -> None: 865 # pylint: disable=too-many-branches 866 try: 867 assert bui.app.classic is not None 868 sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get( 869 'sel_name' 870 ) 871 if sel_name == 'Back': 872 sel = self._back_button 873 else: 874 bui.containerwidget( 875 edit=self._root_widget, selected_child=self._scrollwidget 876 ) 877 if sel_name == 'VRTest': 878 sel = self._vr_test_button 879 elif sel_name == 'NetTest': 880 sel = self._net_test_button 881 elif sel_name == 'SendInfo': 882 sel = self._send_info_button 883 elif sel_name == 'Benchmarks': 884 sel = self._benchmarks_button 885 elif sel_name == 'KickIdlePlayers': 886 sel = self._kick_idle_players_check_box.widget 887 elif sel_name == 'ShowDemosWhenIdle': 888 sel = self._show_demos_when_idle_check_box.widget 889 elif sel_name == 'ShowDeprecatedLoginTypes': 890 sel = self._show_deprecated_login_types_check_box.widget 891 elif sel_name == 'ShowPing': 892 sel = self._show_game_ping_check_box.widget 893 elif sel_name == 'DisableCameraShake': 894 sel = self._disable_camera_shake_check_box.widget 895 elif ( 896 sel_name == 'AlwaysUseInternalKeyboard' 897 and self._always_use_internal_keyboard_check_box is not None 898 ): 899 sel = self._always_use_internal_keyboard_check_box.widget 900 elif ( 901 sel_name == 'DisableGyro' 902 and self._disable_gyro_check_box is not None 903 ): 904 sel = self._disable_gyro_check_box.widget 905 elif ( 906 sel_name == 'Languages' and self._language_popup is not None 907 ): 908 sel = self._language_popup.get_button() 909 elif sel_name == 'TranslationEditor': 910 sel = self._translation_editor_button 911 elif sel_name == 'ShowUserMods': 912 sel = self._show_user_mods_button 913 elif sel_name == 'Plugins': 914 sel = self._plugins_button 915 elif sel_name == 'DevTools': 916 sel = self._dev_tools_button 917 elif sel_name == 'ModdingGuide': 918 sel = self._modding_guide_button 919 elif sel_name == 'LangInform': 920 sel = self._language_inform_checkbox 921 else: 922 sel = None 923 if sel is not None: 924 bui.containerwidget( 925 edit=self._subcontainer, 926 selected_child=sel, 927 visible_child=sel, 928 ) 929 except Exception: 930 logging.exception('Error restoring state for %s.', self) 931 932 def _on_menu_open(self) -> None: 933 self._menu_open = True 934 935 def _on_menu_close(self) -> None: 936 self._menu_open = False 937 938 def _on_menu_choice(self, choice: str) -> None: 939 bui.app.lang.setlanguage(None if choice == 'Auto' else choice) 940 self._save_state() 941 bui.apptimer(0.1, bui.WeakCall(self._rebuild)) 942 943 def _completed_langs_cb(self, results: dict[str, Any] | None) -> None: 944 if results is not None and results['langs'] is not None: 945 self._complete_langs_list = results['langs'] 946 self._complete_langs_error = False 947 else: 948 self._complete_langs_list = None 949 self._complete_langs_error = True 950 bui.apptimer(0.001, bui.WeakCall(self._update_lang_status))
Window for editing advanced app settings.
AdvancedSettingsWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
22 def __init__( 23 self, 24 transition: str | None = 'in_right', 25 origin_widget: bui.Widget | None = None, 26 ): 27 # pylint: disable=too-many-statements 28 29 if bui.app.classic is None: 30 raise RuntimeError('This requires classic support.') 31 32 # Preload some modules we use in a background thread so we won't 33 # have a visual hitch when the user taps them. 34 bui.app.threadpool_submit_no_wait(self._preload_modules) 35 36 app = bui.app 37 assert app.classic is not None 38 39 uiscale = bui.app.ui_v1.uiscale 40 self._width = 1030.0 if uiscale is bui.UIScale.SMALL else 670.0 41 x_inset = 150 if uiscale is bui.UIScale.SMALL else 0 42 self._height = ( 43 390.0 44 if uiscale is bui.UIScale.SMALL 45 else 450.0 if uiscale is bui.UIScale.MEDIUM else 520.0 46 ) 47 self._lang_status_text: bui.Widget | None = None 48 49 self._spacing = 32 50 self._menu_open = False 51 top_extra = 10 if uiscale is bui.UIScale.SMALL else 0 52 53 super().__init__( 54 root_widget=bui.containerwidget( 55 size=(self._width, self._height + top_extra), 56 toolbar_visibility=( 57 'menu_minimal' 58 if uiscale is bui.UIScale.SMALL 59 else 'menu_full' 60 ), 61 scale=( 62 2.04 63 if uiscale is bui.UIScale.SMALL 64 else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0 65 ), 66 stack_offset=( 67 (0, 10) if uiscale is bui.UIScale.SMALL else (0, 0) 68 ), 69 ), 70 transition=transition, 71 origin_widget=origin_widget, 72 ) 73 74 self._prev_lang = '' 75 self._prev_lang_list: list[str] = [] 76 self._complete_langs_list: list | None = None 77 self._complete_langs_error = False 78 self._language_popup: PopupMenu | None = None 79 80 # In vr-mode, the internal keyboard is currently the *only* option, 81 # so no need to show this. 82 self._show_always_use_internal_keyboard = not app.env.vr 83 84 self._scroll_width = self._width - (100 + 2 * x_inset) 85 self._scroll_height = self._height - ( 86 125.0 if uiscale is bui.UIScale.SMALL else 115.0 87 ) 88 self._sub_width = self._scroll_width * 0.95 89 self._sub_height = 870.0 90 91 if self._show_always_use_internal_keyboard: 92 self._sub_height += 62 93 94 self._show_disable_gyro = app.classic.platform in {'ios', 'android'} 95 if self._show_disable_gyro: 96 self._sub_height += 42 97 98 self._do_vr_test_button = app.env.vr 99 self._do_net_test_button = True 100 self._extra_button_spacing = self._spacing * 2.5 101 102 if self._do_vr_test_button: 103 self._sub_height += self._extra_button_spacing 104 if self._do_net_test_button: 105 self._sub_height += self._extra_button_spacing 106 self._sub_height += self._spacing * 2.0 # plugins 107 self._sub_height += self._spacing * 2.0 # dev tools 108 109 self._r = 'settingsWindowAdvanced' 110 111 if uiscale is bui.UIScale.SMALL: 112 bui.containerwidget( 113 edit=self._root_widget, on_cancel_call=self.main_window_back 114 ) 115 self._back_button = None 116 else: 117 self._back_button = bui.buttonwidget( 118 parent=self._root_widget, 119 position=(53 + x_inset, self._height - 60), 120 size=(140, 60), 121 scale=0.8, 122 autoselect=True, 123 label=bui.Lstr(resource='backText'), 124 button_type='back', 125 on_activate_call=self.main_window_back, 126 ) 127 bui.containerwidget( 128 edit=self._root_widget, cancel_button=self._back_button 129 ) 130 131 self._title_text = bui.textwidget( 132 parent=self._root_widget, 133 position=( 134 self._width * 0.5, 135 self._height - (61 if uiscale is bui.UIScale.SMALL else 40), 136 ), 137 size=(0, 0), 138 scale=0.85 if uiscale is bui.UIScale.SMALL else 1.0, 139 text=bui.Lstr(resource=f'{self._r}.titleText'), 140 color=app.ui_v1.title_color, 141 h_align='center', 142 v_align='center', 143 ) 144 145 if self._back_button is not None: 146 bui.buttonwidget( 147 edit=self._back_button, 148 button_type='backSmall', 149 size=(60, 60), 150 label=bui.charstr(bui.SpecialChar.BACK), 151 ) 152 153 self._scrollwidget = bui.scrollwidget( 154 parent=self._root_widget, 155 position=(50 + x_inset, 50), 156 simple_culling_v=20.0, 157 highlight=False, 158 size=(self._scroll_width, self._scroll_height), 159 selection_loops_to_parent=True, 160 ) 161 bui.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) 162 self._subcontainer = bui.containerwidget( 163 parent=self._scrollwidget, 164 size=(self._sub_width, self._sub_height), 165 background=False, 166 selection_loops_to_parent=True, 167 ) 168 169 self._rebuild() 170 171 # Rebuild periodically to pick up language changes/additions/etc. 172 self._rebuild_timer = bui.AppTimer( 173 1.0, bui.WeakCall(self._rebuild), repeat=True 174 ) 175 176 # Fetch the list of completed languages. 177 bui.app.classic.master_server_v1_get( 178 'bsLangGetCompleted', 179 {'b': app.env.engine_build_number}, 180 callback=bui.WeakCall(self._completed_langs_cb), 181 )
Create a MainWindow given a root widget and transition info.
Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.
183 @override 184 def get_main_window_state(self) -> bui.MainWindowState: 185 # Support recreating our window for back/refresh purposes. 186 cls = type(self) 187 return bui.BasicMainWindowState( 188 create_call=lambda transition, origin_widget: cls( 189 transition=transition, origin_widget=origin_widget 190 ) 191 )
Return a WindowState to recreate this window, if supported.
@override
def
on_main_window_close(self) -> None:
Called before transitioning out a main window.
A good opportunity to save window state/etc.
Inherited Members
- bauiv1._uitypes.MainWindow
- main_window_back_state
- main_window_is_top_level
- main_window_is_auxiliary
- main_window_close
- main_window_has_control
- main_window_back
- main_window_replace
- bauiv1._uitypes.Window
- get_root_widget