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