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