mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2026-08-15 13:04:50 +00:00
Initial commit
This commit is contained in:
parent
bc49523c99
commit
44d606cce7
1929 changed files with 612166 additions and 0 deletions
1
dist/ba_data/python/bastd/ui/settings/__init__.py
vendored
Normal file
1
dist/ba_data/python/bastd/ui/settings/__init__.py
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
BIN
dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
865
dist/ba_data/python/bastd/ui/settings/advanced.py
vendored
Normal file
865
dist/ba_data/python/bastd/ui/settings/advanced.py
vendored
Normal file
|
|
@ -0,0 +1,865 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality for advanced settings."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
from bastd.ui import popup as popup_ui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class AdvancedSettingsWindow(ba.Window):
|
||||
"""Window for editing advanced game settings."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str = 'in_right',
|
||||
origin_widget: ba.Widget | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
from ba.internal import master_server_get
|
||||
import threading
|
||||
|
||||
# Preload some modules we use in a background thread so we won't
|
||||
# have a visual hitch when the user taps them.
|
||||
threading.Thread(target=self._preload_modules).start()
|
||||
|
||||
app = ba.app
|
||||
|
||||
# If they provided an origin-widget, scale up from that.
|
||||
scale_origin: tuple[float, float] | None
|
||||
if origin_widget is not None:
|
||||
self._transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
|
||||
uiscale = ba.app.ui.uiscale
|
||||
self._width = 870.0 if uiscale is ba.UIScale.SMALL else 670.0
|
||||
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
|
||||
self._height = (
|
||||
390.0
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 450.0
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 520.0
|
||||
)
|
||||
self._spacing = 32
|
||||
self._menu_open = False
|
||||
top_extra = 10 if uiscale is ba.UIScale.SMALL else 0
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height + top_extra),
|
||||
transition=transition,
|
||||
toolbar_visibility='menu_minimal',
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
scale=(
|
||||
2.06
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.4
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(0, -25)
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else (0, 0),
|
||||
)
|
||||
)
|
||||
|
||||
self._prev_lang = ''
|
||||
self._prev_lang_list: list[str] = []
|
||||
self._complete_langs_list: list | None = None
|
||||
self._complete_langs_error = False
|
||||
self._language_popup: popup_ui.PopupMenu | None = None
|
||||
|
||||
# In vr-mode, the internal keyboard is currently the *only* option,
|
||||
# so no need to show this.
|
||||
self._show_always_use_internal_keyboard = (
|
||||
not app.vr_mode and not app.iircade_mode
|
||||
)
|
||||
|
||||
self._scroll_width = self._width - (100 + 2 * x_inset)
|
||||
self._scroll_height = self._height - 115.0
|
||||
self._sub_width = self._scroll_width * 0.95
|
||||
self._sub_height = 724.0
|
||||
|
||||
if self._show_always_use_internal_keyboard:
|
||||
self._sub_height += 62
|
||||
|
||||
self._show_disable_gyro = app.platform in {'ios', 'android'}
|
||||
if self._show_disable_gyro:
|
||||
self._sub_height += 42
|
||||
|
||||
self._do_vr_test_button = app.vr_mode
|
||||
self._do_net_test_button = True
|
||||
self._extra_button_spacing = self._spacing * 2.5
|
||||
|
||||
if self._do_vr_test_button:
|
||||
self._sub_height += self._extra_button_spacing
|
||||
if self._do_net_test_button:
|
||||
self._sub_height += self._extra_button_spacing
|
||||
self._sub_height += self._spacing * 2.0 # plugins
|
||||
|
||||
self._r = 'settingsWindowAdvanced'
|
||||
|
||||
if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=self._do_back
|
||||
)
|
||||
self._back_button = None
|
||||
else:
|
||||
self._back_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(53 + x_inset, self._height - 60),
|
||||
size=(140, 60),
|
||||
scale=0.8,
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
on_activate_call=self._do_back,
|
||||
)
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._back_button
|
||||
)
|
||||
|
||||
self._title_text = ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, self._height - 52),
|
||||
size=(self._width, 25),
|
||||
text=ba.Lstr(resource=f'{self._r}.titleText'),
|
||||
color=app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
)
|
||||
|
||||
if self._back_button is not None:
|
||||
ba.buttonwidget(
|
||||
edit=self._back_button,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
self._scrollwidget = ba.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
position=(50 + x_inset, 50),
|
||||
simple_culling_v=20.0,
|
||||
highlight=False,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
|
||||
self._subcontainer = ba.containerwidget(
|
||||
parent=self._scrollwidget,
|
||||
size=(self._sub_width, self._sub_height),
|
||||
background=False,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
|
||||
self._rebuild()
|
||||
|
||||
# Rebuild periodically to pick up language changes/additions/etc.
|
||||
self._rebuild_timer = ba.Timer(
|
||||
1.0,
|
||||
ba.WeakCall(self._rebuild),
|
||||
repeat=True,
|
||||
timetype=ba.TimeType.REAL,
|
||||
)
|
||||
|
||||
# Fetch the list of completed languages.
|
||||
master_server_get(
|
||||
'bsLangGetCompleted',
|
||||
{'b': app.build_number},
|
||||
callback=ba.WeakCall(self._completed_langs_cb),
|
||||
)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
@staticmethod
|
||||
def _preload_modules() -> None:
|
||||
"""Preload modules we use (called in bg thread)."""
|
||||
from bastd.ui import config as _unused1
|
||||
from ba import modutils as _unused2
|
||||
from bastd.ui.settings import vrtesting as _unused3
|
||||
from bastd.ui.settings import nettesting as _unused4
|
||||
from bastd.ui import appinvite as _unused5
|
||||
from bastd.ui import account as _unused6
|
||||
from bastd.ui import promocode as _unused7
|
||||
from bastd.ui import debug as _unused8
|
||||
from bastd.ui.settings import plugins as _unused9
|
||||
|
||||
def _update_lang_status(self) -> None:
|
||||
if self._complete_langs_list is not None:
|
||||
up_to_date = ba.app.lang.language in self._complete_langs_list
|
||||
ba.textwidget(
|
||||
edit=self._lang_status_text,
|
||||
text=''
|
||||
if ba.app.lang.language == 'Test'
|
||||
else ba.Lstr(
|
||||
resource=f'{self._r}.translationNoUpdateNeededText'
|
||||
)
|
||||
if up_to_date
|
||||
else ba.Lstr(resource=f'{self._r}.translationUpdateNeededText'),
|
||||
color=(0.2, 1.0, 0.2, 0.8)
|
||||
if up_to_date
|
||||
else (1.0, 0.2, 0.2, 0.8),
|
||||
)
|
||||
else:
|
||||
ba.textwidget(
|
||||
edit=self._lang_status_text,
|
||||
text=ba.Lstr(resource=f'{self._r}.translationFetchErrorText')
|
||||
if self._complete_langs_error
|
||||
else ba.Lstr(
|
||||
resource=f'{self._r}.translationFetchingStatusText'
|
||||
),
|
||||
color=(1.0, 0.5, 0.2)
|
||||
if self._complete_langs_error
|
||||
else (0.7, 0.7, 0.7),
|
||||
)
|
||||
|
||||
def _rebuild(self) -> None:
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
from bastd.ui.config import ConfigCheckBox
|
||||
from ba.modutils import show_user_scripts
|
||||
|
||||
available_languages = ba.app.lang.available_languages
|
||||
|
||||
# Don't rebuild if the menu is open or if our language and
|
||||
# language-list hasn't changed.
|
||||
# NOTE - although we now support widgets updating their own
|
||||
# translations, we still change the label formatting on the language
|
||||
# menu based on the language so still need this. ...however we could
|
||||
# make this more limited to it only rebuilds that one menu instead
|
||||
# of everything.
|
||||
if self._menu_open or (
|
||||
self._prev_lang == ba.app.config.get('Lang', None)
|
||||
and self._prev_lang_list == available_languages
|
||||
):
|
||||
return
|
||||
self._prev_lang = ba.app.config.get('Lang', None)
|
||||
self._prev_lang_list = available_languages
|
||||
|
||||
# Clear out our sub-container.
|
||||
children = self._subcontainer.get_children()
|
||||
for child in children:
|
||||
child.delete()
|
||||
|
||||
v = self._sub_height - 35
|
||||
|
||||
v -= self._spacing * 1.2
|
||||
|
||||
# Update our existing back button and title.
|
||||
if self._back_button is not None:
|
||||
ba.buttonwidget(
|
||||
edit=self._back_button, label=ba.Lstr(resource='backText')
|
||||
)
|
||||
ba.buttonwidget(
|
||||
edit=self._back_button, label=ba.charstr(ba.SpecialChar.BACK)
|
||||
)
|
||||
|
||||
ba.textwidget(
|
||||
edit=self._title_text, text=ba.Lstr(resource=f'{self._r}.titleText')
|
||||
)
|
||||
|
||||
this_button_width = 410
|
||||
|
||||
self._promo_code_button = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 14),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=f'{self._r}.enterPromoCodeText'),
|
||||
text_scale=1.0,
|
||||
on_activate_call=self._on_promo_code_press,
|
||||
)
|
||||
if self._back_button is not None:
|
||||
ba.widget(
|
||||
edit=self._promo_code_button,
|
||||
up_widget=self._back_button,
|
||||
left_widget=self._back_button,
|
||||
)
|
||||
v -= self._extra_button_spacing * 0.8
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(200, v + 10),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=f'{self._r}.languageText'),
|
||||
maxwidth=150,
|
||||
scale=0.95,
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
languages = ba.app.lang.available_languages
|
||||
cur_lang = ba.app.config.get('Lang', None)
|
||||
if cur_lang is None:
|
||||
cur_lang = 'Auto'
|
||||
|
||||
# We have a special dict of language names in that language
|
||||
# so we don't have to go digging through each full language.
|
||||
try:
|
||||
import json
|
||||
|
||||
with open('ba_data/data/langdata.json', encoding='utf-8') as infile:
|
||||
lang_names_translated = json.loads(infile.read())[
|
||||
'lang_names_translated'
|
||||
]
|
||||
except Exception:
|
||||
ba.print_exception('Error reading lang data.')
|
||||
lang_names_translated = {}
|
||||
|
||||
langs_translated = {}
|
||||
for lang in languages:
|
||||
langs_translated[lang] = lang_names_translated.get(lang, lang)
|
||||
|
||||
langs_full = {}
|
||||
for lang in languages:
|
||||
lang_translated = ba.Lstr(translate=('languages', lang)).evaluate()
|
||||
if langs_translated[lang] == lang_translated:
|
||||
langs_full[lang] = lang_translated
|
||||
else:
|
||||
langs_full[lang] = (
|
||||
langs_translated[lang] + ' (' + lang_translated + ')'
|
||||
)
|
||||
|
||||
self._language_popup = popup_ui.PopupMenu(
|
||||
parent=self._subcontainer,
|
||||
position=(210, v - 19),
|
||||
width=150,
|
||||
opening_call=ba.WeakCall(self._on_menu_open),
|
||||
closing_call=ba.WeakCall(self._on_menu_close),
|
||||
autoselect=False,
|
||||
on_value_change_call=ba.WeakCall(self._on_menu_choice),
|
||||
choices=['Auto'] + languages,
|
||||
button_size=(250, 60),
|
||||
choices_display=(
|
||||
[
|
||||
ba.Lstr(
|
||||
value=(
|
||||
ba.Lstr(resource='autoText').evaluate()
|
||||
+ ' ('
|
||||
+ ba.Lstr(
|
||||
translate=(
|
||||
'languages',
|
||||
ba.app.lang.default_language,
|
||||
)
|
||||
).evaluate()
|
||||
+ ')'
|
||||
)
|
||||
)
|
||||
]
|
||||
+ [ba.Lstr(value=langs_full[l]) for l in languages]
|
||||
),
|
||||
current_choice=cur_lang,
|
||||
)
|
||||
|
||||
v -= self._spacing * 1.8
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v + 10),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(
|
||||
resource=f'{self._r}.helpTranslateText',
|
||||
subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
|
||||
),
|
||||
maxwidth=self._sub_width * 0.9,
|
||||
max_height=55,
|
||||
flatness=1.0,
|
||||
scale=0.65,
|
||||
color=(0.4, 0.9, 0.4, 0.8),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= self._spacing * 1.9
|
||||
this_button_width = 410
|
||||
self._translation_editor_button = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 24),
|
||||
size=(this_button_width, 60),
|
||||
label=ba.Lstr(
|
||||
resource=f'{self._r}.translationEditorButtonText',
|
||||
subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
|
||||
),
|
||||
autoselect=True,
|
||||
on_activate_call=ba.Call(
|
||||
ba.open_url, 'https://legacy.ballistica.net/translate'
|
||||
),
|
||||
)
|
||||
|
||||
self._lang_status_text = ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 40),
|
||||
size=(0, 0),
|
||||
text='',
|
||||
flatness=1.0,
|
||||
scale=0.63,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=400.0,
|
||||
)
|
||||
self._update_lang_status()
|
||||
v -= 40
|
||||
|
||||
lang_inform = ba.internal.get_v1_account_misc_val('langInform', False)
|
||||
|
||||
self._language_inform_checkbox = cbw = ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(50, v - 50),
|
||||
size=(self._sub_width - 100, 30),
|
||||
autoselect=True,
|
||||
maxwidth=430,
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
value=lang_inform,
|
||||
text=ba.Lstr(resource=f'{self._r}.translationInformMe'),
|
||||
on_value_change_call=ba.WeakCall(self._on_lang_inform_value_change),
|
||||
)
|
||||
|
||||
ba.widget(
|
||||
edit=self._translation_editor_button,
|
||||
down_widget=cbw,
|
||||
up_widget=self._language_popup.get_button(),
|
||||
)
|
||||
|
||||
v -= self._spacing * 3.0
|
||||
|
||||
self._kick_idle_players_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Kick Idle Players',
|
||||
displayname=ba.Lstr(resource=f'{self._r}.kickIdlePlayersText'),
|
||||
scale=1.0,
|
||||
maxwidth=430,
|
||||
)
|
||||
|
||||
v -= 42
|
||||
self._show_game_ping_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Show Ping',
|
||||
displayname=ba.Lstr(resource=f'{self._r}.showInGamePingText'),
|
||||
scale=1.0,
|
||||
maxwidth=430,
|
||||
)
|
||||
|
||||
v -= 42
|
||||
self._disable_camera_shake_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Disable Camera Shake',
|
||||
displayname=ba.Lstr(resource=f'{self._r}.disableCameraShakeText'),
|
||||
scale=1.0,
|
||||
maxwidth=430,
|
||||
)
|
||||
|
||||
self._disable_gyro_check_box: ConfigCheckBox | None = None
|
||||
if self._show_disable_gyro:
|
||||
v -= 42
|
||||
self._disable_gyro_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Disable Camera Gyro',
|
||||
displayname=ba.Lstr(
|
||||
resource=f'{self._r}.disableCameraGyroscopeMotionText'
|
||||
),
|
||||
scale=1.0,
|
||||
maxwidth=430,
|
||||
)
|
||||
|
||||
self._always_use_internal_keyboard_check_box: ConfigCheckBox | None
|
||||
if self._show_always_use_internal_keyboard:
|
||||
v -= 42
|
||||
self._always_use_internal_keyboard_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Always Use Internal Keyboard',
|
||||
autoselect=True,
|
||||
displayname=ba.Lstr(
|
||||
resource=f'{self._r}.alwaysUseInternalKeyboardText'
|
||||
),
|
||||
scale=1.0,
|
||||
maxwidth=430,
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(90, v - 10),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(
|
||||
resource=(
|
||||
f'{self._r}.alwaysUseInternalKeyboardDescriptionText'
|
||||
)
|
||||
),
|
||||
maxwidth=400,
|
||||
flatness=1.0,
|
||||
scale=0.65,
|
||||
color=(0.4, 0.9, 0.4, 0.8),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
)
|
||||
v -= 20
|
||||
else:
|
||||
self._always_use_internal_keyboard_check_box = None
|
||||
|
||||
v -= self._spacing * 2.1
|
||||
|
||||
this_button_width = 410
|
||||
self._modding_guide_button = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 10),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=f'{self._r}.moddingGuideText'),
|
||||
text_scale=1.0,
|
||||
on_activate_call=ba.Call(
|
||||
ba.open_url, 'https://ballistica.net/wiki/modding-guide'
|
||||
),
|
||||
)
|
||||
if self._show_always_use_internal_keyboard:
|
||||
assert self._always_use_internal_keyboard_check_box is not None
|
||||
ba.widget(
|
||||
edit=self._always_use_internal_keyboard_check_box.widget,
|
||||
down_widget=self._modding_guide_button,
|
||||
)
|
||||
ba.widget(
|
||||
edit=self._modding_guide_button,
|
||||
up_widget=self._always_use_internal_keyboard_check_box.widget,
|
||||
)
|
||||
else:
|
||||
ba.widget(
|
||||
edit=self._modding_guide_button,
|
||||
up_widget=self._kick_idle_players_check_box.widget,
|
||||
)
|
||||
ba.widget(
|
||||
edit=self._kick_idle_players_check_box.widget,
|
||||
down_widget=self._modding_guide_button,
|
||||
)
|
||||
|
||||
v -= self._spacing * 2.0
|
||||
|
||||
self._show_user_mods_button = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 10),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=f'{self._r}.showUserModsText'),
|
||||
text_scale=1.0,
|
||||
on_activate_call=show_user_scripts,
|
||||
)
|
||||
|
||||
v -= self._spacing * 2.0
|
||||
|
||||
self._plugins_button = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 10),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='pluginsText'),
|
||||
text_scale=1.0,
|
||||
on_activate_call=self._on_plugins_button_press,
|
||||
)
|
||||
|
||||
v -= self._spacing * 0.6
|
||||
|
||||
self._vr_test_button: ba.Widget | None
|
||||
if self._do_vr_test_button:
|
||||
v -= self._extra_button_spacing
|
||||
self._vr_test_button = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 14),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=f'{self._r}.vrTestingText'),
|
||||
text_scale=1.0,
|
||||
on_activate_call=self._on_vr_test_press,
|
||||
)
|
||||
else:
|
||||
self._vr_test_button = None
|
||||
|
||||
self._net_test_button: ba.Widget | None
|
||||
if self._do_net_test_button:
|
||||
v -= self._extra_button_spacing
|
||||
self._net_test_button = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 14),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=f'{self._r}.netTestingText'),
|
||||
text_scale=1.0,
|
||||
on_activate_call=self._on_net_test_press,
|
||||
)
|
||||
else:
|
||||
self._net_test_button = None
|
||||
|
||||
v -= 70
|
||||
self._benchmarks_button = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 14),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=f'{self._r}.benchmarksText'),
|
||||
text_scale=1.0,
|
||||
on_activate_call=self._on_benchmark_press,
|
||||
)
|
||||
|
||||
for child in self._subcontainer.get_children():
|
||||
ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20)
|
||||
|
||||
if ba.app.ui.use_toolbars:
|
||||
pbtn = ba.internal.get_special_widget('party_button')
|
||||
ba.widget(edit=self._scrollwidget, right_widget=pbtn)
|
||||
if self._back_button is None:
|
||||
ba.widget(
|
||||
edit=self._scrollwidget,
|
||||
left_widget=ba.internal.get_special_widget('back_button'),
|
||||
)
|
||||
|
||||
self._restore_state()
|
||||
|
||||
def _show_restart_needed(self, value: Any) -> None:
|
||||
del value # Unused.
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource=f'{self._r}.mustRestartText'), color=(1, 1, 0)
|
||||
)
|
||||
|
||||
def _on_lang_inform_value_change(self, val: bool) -> None:
|
||||
ba.internal.add_transaction(
|
||||
{'type': 'SET_MISC_VAL', 'name': 'langInform', 'value': val}
|
||||
)
|
||||
ba.internal.run_transactions()
|
||||
|
||||
def _on_vr_test_press(self) -> None:
|
||||
from bastd.ui.settings.vrtesting import VRTestingWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
VRTestingWindow(transition='in_right').get_root_widget()
|
||||
)
|
||||
|
||||
def _on_net_test_press(self) -> None:
|
||||
from bastd.ui.settings.nettesting import NetTestingWindow
|
||||
|
||||
# Net-testing requires a signed in v1 account.
|
||||
if ba.internal.get_v1_account_state() != 'signed_in':
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
|
||||
)
|
||||
ba.playsound(ba.getsound('error'))
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
NetTestingWindow(transition='in_right').get_root_widget()
|
||||
)
|
||||
|
||||
def _on_friend_promo_code_press(self) -> None:
|
||||
from bastd.ui import appinvite
|
||||
from bastd.ui import account
|
||||
|
||||
if ba.internal.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
return
|
||||
appinvite.handle_app_invites_press()
|
||||
|
||||
def _on_plugins_button_press(self) -> None:
|
||||
from bastd.ui.settings.plugins import PluginWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
PluginWindow(origin_widget=self._plugins_button).get_root_widget()
|
||||
)
|
||||
|
||||
def _on_promo_code_press(self) -> None:
|
||||
from bastd.ui.promocode import PromoCodeWindow
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
|
||||
# We have to be logged in for promo-codes to work.
|
||||
if ba.internal.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
PromoCodeWindow(
|
||||
origin_widget=self._promo_code_button
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _on_benchmark_press(self) -> None:
|
||||
from bastd.ui.debug import DebugWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
DebugWindow(transition='in_right').get_root_widget()
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
# pylint: disable=too-many-branches
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._scrollwidget:
|
||||
sel = self._subcontainer.get_selected_child()
|
||||
if sel == self._vr_test_button:
|
||||
sel_name = 'VRTest'
|
||||
elif sel == self._net_test_button:
|
||||
sel_name = 'NetTest'
|
||||
elif sel == self._promo_code_button:
|
||||
sel_name = 'PromoCode'
|
||||
elif sel == self._benchmarks_button:
|
||||
sel_name = 'Benchmarks'
|
||||
elif sel == self._kick_idle_players_check_box.widget:
|
||||
sel_name = 'KickIdlePlayers'
|
||||
elif sel == self._show_game_ping_check_box.widget:
|
||||
sel_name = 'ShowPing'
|
||||
elif sel == self._disable_camera_shake_check_box.widget:
|
||||
sel_name = 'DisableCameraShake'
|
||||
elif (
|
||||
self._always_use_internal_keyboard_check_box is not None
|
||||
and sel
|
||||
== self._always_use_internal_keyboard_check_box.widget
|
||||
):
|
||||
sel_name = 'AlwaysUseInternalKeyboard'
|
||||
elif (
|
||||
self._disable_gyro_check_box is not None
|
||||
and sel == self._disable_gyro_check_box.widget
|
||||
):
|
||||
sel_name = 'DisableGyro'
|
||||
elif (
|
||||
self._language_popup is not None
|
||||
and sel == self._language_popup.get_button()
|
||||
):
|
||||
sel_name = 'Languages'
|
||||
elif sel == self._translation_editor_button:
|
||||
sel_name = 'TranslationEditor'
|
||||
elif sel == self._show_user_mods_button:
|
||||
sel_name = 'ShowUserMods'
|
||||
elif sel == self._plugins_button:
|
||||
sel_name = 'Plugins'
|
||||
elif sel == self._modding_guide_button:
|
||||
sel_name = 'ModdingGuide'
|
||||
elif sel == self._language_inform_checkbox:
|
||||
sel_name = 'LangInform'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection \'{sel}\'')
|
||||
elif sel == self._back_button:
|
||||
sel_name = 'Back'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection \'{sel}\'')
|
||||
ba.app.ui.window_states[type(self)] = {'sel_name': sel_name}
|
||||
except Exception:
|
||||
ba.print_exception(f'Error saving state for {self.__class__}')
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
# pylint: disable=too-many-branches
|
||||
try:
|
||||
sel_name = ba.app.ui.window_states.get(type(self), {}).get(
|
||||
'sel_name'
|
||||
)
|
||||
if sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
else:
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._scrollwidget
|
||||
)
|
||||
if sel_name == 'VRTest':
|
||||
sel = self._vr_test_button
|
||||
elif sel_name == 'NetTest':
|
||||
sel = self._net_test_button
|
||||
elif sel_name == 'PromoCode':
|
||||
sel = self._promo_code_button
|
||||
elif sel_name == 'Benchmarks':
|
||||
sel = self._benchmarks_button
|
||||
elif sel_name == 'KickIdlePlayers':
|
||||
sel = self._kick_idle_players_check_box.widget
|
||||
elif sel_name == 'ShowPing':
|
||||
sel = self._show_game_ping_check_box.widget
|
||||
elif sel_name == 'DisableCameraShake':
|
||||
sel = self._disable_camera_shake_check_box.widget
|
||||
elif (
|
||||
sel_name == 'AlwaysUseInternalKeyboard'
|
||||
and self._always_use_internal_keyboard_check_box is not None
|
||||
):
|
||||
sel = self._always_use_internal_keyboard_check_box.widget
|
||||
elif (
|
||||
sel_name == 'DisableGyro'
|
||||
and self._disable_gyro_check_box is not None
|
||||
):
|
||||
sel = self._disable_gyro_check_box.widget
|
||||
elif (
|
||||
sel_name == 'Languages' and self._language_popup is not None
|
||||
):
|
||||
sel = self._language_popup.get_button()
|
||||
elif sel_name == 'TranslationEditor':
|
||||
sel = self._translation_editor_button
|
||||
elif sel_name == 'ShowUserMods':
|
||||
sel = self._show_user_mods_button
|
||||
elif sel_name == 'Plugins':
|
||||
sel = self._plugins_button
|
||||
elif sel_name == 'ModdingGuide':
|
||||
sel = self._modding_guide_button
|
||||
elif sel_name == 'LangInform':
|
||||
sel = self._language_inform_checkbox
|
||||
else:
|
||||
sel = None
|
||||
if sel is not None:
|
||||
ba.containerwidget(
|
||||
edit=self._subcontainer,
|
||||
selected_child=sel,
|
||||
visible_child=sel,
|
||||
)
|
||||
except Exception:
|
||||
ba.print_exception(f'Error restoring state for {self.__class__}')
|
||||
|
||||
def _on_menu_open(self) -> None:
|
||||
self._menu_open = True
|
||||
|
||||
def _on_menu_close(self) -> None:
|
||||
self._menu_open = False
|
||||
|
||||
def _on_menu_choice(self, choice: str) -> None:
|
||||
ba.app.lang.setlanguage(None if choice == 'Auto' else choice)
|
||||
self._save_state()
|
||||
ba.timer(0.1, ba.WeakCall(self._rebuild), timetype=ba.TimeType.REAL)
|
||||
|
||||
def _completed_langs_cb(self, results: dict[str, Any] | None) -> None:
|
||||
if results is not None and results['langs'] is not None:
|
||||
self._complete_langs_list = results['langs']
|
||||
self._complete_langs_error = False
|
||||
else:
|
||||
self._complete_langs_list = None
|
||||
self._complete_langs_error = True
|
||||
ba.timer(
|
||||
0.001,
|
||||
ba.WeakCall(self._update_lang_status),
|
||||
timetype=ba.TimeType.REAL,
|
||||
)
|
||||
|
||||
def _do_back(self) -> None:
|
||||
from bastd.ui.settings.allsettings import AllSettingsWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
ba.app.ui.set_main_menu_window(
|
||||
AllSettingsWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
331
dist/ba_data/python/bastd/ui/settings/allsettings.py
vendored
Normal file
331
dist/ba_data/python/bastd/ui/settings/allsettings.py
vendored
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI for top level settings categories."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class AllSettingsWindow(ba.Window):
|
||||
"""Window for selecting a settings category."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str = 'in_right',
|
||||
origin_widget: ba.Widget | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-locals
|
||||
import threading
|
||||
|
||||
# Preload some modules we use in a background thread so we won't
|
||||
# have a visual hitch when the user taps them.
|
||||
threading.Thread(target=self._preload_modules).start()
|
||||
|
||||
ba.set_analytics_screen('Settings Window')
|
||||
scale_origin: tuple[float, float] | None
|
||||
if origin_widget is not None:
|
||||
self._transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
uiscale = ba.app.ui.uiscale
|
||||
width = 900 if uiscale is ba.UIScale.SMALL else 580
|
||||
x_inset = 75 if uiscale is ba.UIScale.SMALL else 0
|
||||
height = 435
|
||||
self._r = 'settingsWindow'
|
||||
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
|
||||
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(width, height + top_extra),
|
||||
transition=transition,
|
||||
toolbar_visibility='menu_minimal',
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
scale=(
|
||||
1.75
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.35
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0),
|
||||
)
|
||||
)
|
||||
|
||||
if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
|
||||
self._back_button = None
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=self._do_back
|
||||
)
|
||||
else:
|
||||
self._back_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
position=(40 + x_inset, height - 55),
|
||||
size=(130, 60),
|
||||
scale=0.8,
|
||||
text_scale=1.2,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
on_activate_call=self._do_back,
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, height - 44),
|
||||
size=(width, 25),
|
||||
text=ba.Lstr(resource=self._r + '.titleText'),
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=130,
|
||||
)
|
||||
|
||||
if self._back_button is not None:
|
||||
ba.buttonwidget(
|
||||
edit=self._back_button,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
v = height - 80
|
||||
v -= 145
|
||||
|
||||
basew = 280 if uiscale is ba.UIScale.SMALL else 230
|
||||
baseh = 170
|
||||
x_offs = (
|
||||
x_inset + (105 if uiscale is ba.UIScale.SMALL else 72) - basew
|
||||
) # now unused
|
||||
x_offs2 = x_offs + basew - 7
|
||||
x_offs3 = x_offs + 2 * (basew - 7)
|
||||
x_offs4 = x_offs2
|
||||
x_offs5 = x_offs3
|
||||
|
||||
def _b_title(
|
||||
x: float, y: float, button: ba.Widget, text: str | ba.Lstr
|
||||
) -> None:
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=text,
|
||||
position=(x + basew * 0.47, y + baseh * 0.22),
|
||||
maxwidth=basew * 0.7,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=button,
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
)
|
||||
|
||||
ctb = self._controllers_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
position=(x_offs2, v),
|
||||
size=(basew, baseh),
|
||||
button_type='square',
|
||||
label='',
|
||||
on_activate_call=self._do_controllers,
|
||||
)
|
||||
if ba.app.ui.use_toolbars and self._back_button is None:
|
||||
bbtn = ba.internal.get_special_widget('back_button')
|
||||
ba.widget(edit=ctb, left_widget=bbtn)
|
||||
_b_title(
|
||||
x_offs2, v, ctb, ba.Lstr(resource=self._r + '.controllersText')
|
||||
)
|
||||
imgw = imgh = 130
|
||||
ba.imagewidget(
|
||||
parent=self._root_widget,
|
||||
position=(x_offs2 + basew * 0.49 - imgw * 0.5, v + 35),
|
||||
size=(imgw, imgh),
|
||||
texture=ba.gettexture('controllerIcon'),
|
||||
draw_controller=ctb,
|
||||
)
|
||||
|
||||
gfxb = self._graphics_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
position=(x_offs3, v),
|
||||
size=(basew, baseh),
|
||||
button_type='square',
|
||||
label='',
|
||||
on_activate_call=self._do_graphics,
|
||||
)
|
||||
if ba.app.ui.use_toolbars:
|
||||
pbtn = ba.internal.get_special_widget('party_button')
|
||||
ba.widget(edit=gfxb, up_widget=pbtn, right_widget=pbtn)
|
||||
_b_title(x_offs3, v, gfxb, ba.Lstr(resource=self._r + '.graphicsText'))
|
||||
imgw = imgh = 110
|
||||
ba.imagewidget(
|
||||
parent=self._root_widget,
|
||||
position=(x_offs3 + basew * 0.49 - imgw * 0.5, v + 42),
|
||||
size=(imgw, imgh),
|
||||
texture=ba.gettexture('graphicsIcon'),
|
||||
draw_controller=gfxb,
|
||||
)
|
||||
|
||||
v -= baseh - 5
|
||||
|
||||
abtn = self._audio_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
position=(x_offs4, v),
|
||||
size=(basew, baseh),
|
||||
button_type='square',
|
||||
label='',
|
||||
on_activate_call=self._do_audio,
|
||||
)
|
||||
_b_title(x_offs4, v, abtn, ba.Lstr(resource=self._r + '.audioText'))
|
||||
imgw = imgh = 120
|
||||
ba.imagewidget(
|
||||
parent=self._root_widget,
|
||||
position=(x_offs4 + basew * 0.49 - imgw * 0.5 + 5, v + 35),
|
||||
size=(imgw, imgh),
|
||||
color=(1, 1, 0),
|
||||
texture=ba.gettexture('audioIcon'),
|
||||
draw_controller=abtn,
|
||||
)
|
||||
|
||||
avb = self._advanced_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
position=(x_offs5, v),
|
||||
size=(basew, baseh),
|
||||
button_type='square',
|
||||
label='',
|
||||
on_activate_call=self._do_advanced,
|
||||
)
|
||||
_b_title(x_offs5, v, avb, ba.Lstr(resource=self._r + '.advancedText'))
|
||||
imgw = imgh = 120
|
||||
ba.imagewidget(
|
||||
parent=self._root_widget,
|
||||
position=(x_offs5 + basew * 0.49 - imgw * 0.5 + 5, v + 35),
|
||||
size=(imgw, imgh),
|
||||
color=(0.8, 0.95, 1),
|
||||
texture=ba.gettexture('advancedIcon'),
|
||||
draw_controller=avb,
|
||||
)
|
||||
self._restore_state()
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
@staticmethod
|
||||
def _preload_modules() -> None:
|
||||
"""Preload modules we use (called in bg thread)."""
|
||||
import bastd.ui.mainmenu as _unused1
|
||||
import bastd.ui.settings.controls as _unused2
|
||||
import bastd.ui.settings.graphics as _unused3
|
||||
import bastd.ui.settings.audio as _unused4
|
||||
import bastd.ui.settings.advanced as _unused5
|
||||
|
||||
def _do_back(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.mainmenu import MainMenuWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
ba.app.ui.set_main_menu_window(
|
||||
MainMenuWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
|
||||
def _do_controllers(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.controls import ControlsSettingsWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
ControlsSettingsWindow(
|
||||
origin_widget=self._controllers_button
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _do_graphics(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.graphics import GraphicsSettingsWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
GraphicsSettingsWindow(
|
||||
origin_widget=self._graphics_button
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _do_audio(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.audio import AudioSettingsWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
AudioSettingsWindow(
|
||||
origin_widget=self._audio_button
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _do_advanced(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
AdvancedSettingsWindow(
|
||||
origin_widget=self._advanced_button
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._controllers_button:
|
||||
sel_name = 'Controllers'
|
||||
elif sel == self._graphics_button:
|
||||
sel_name = 'Graphics'
|
||||
elif sel == self._audio_button:
|
||||
sel_name = 'Audio'
|
||||
elif sel == self._advanced_button:
|
||||
sel_name = 'Advanced'
|
||||
elif sel == self._back_button:
|
||||
sel_name = 'Back'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection \'{sel}\'')
|
||||
ba.app.ui.window_states[type(self)] = {'sel_name': sel_name}
|
||||
except Exception:
|
||||
ba.print_exception(f'Error saving state for {self}.')
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
sel_name = ba.app.ui.window_states.get(type(self), {}).get(
|
||||
'sel_name'
|
||||
)
|
||||
sel: ba.Widget | None
|
||||
if sel_name == 'Controllers':
|
||||
sel = self._controllers_button
|
||||
elif sel_name == 'Graphics':
|
||||
sel = self._graphics_button
|
||||
elif sel_name == 'Audio':
|
||||
sel = self._audio_button
|
||||
elif sel_name == 'Advanced':
|
||||
sel = self._advanced_button
|
||||
elif sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
else:
|
||||
sel = self._controllers_button
|
||||
if sel is not None:
|
||||
ba.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
except Exception:
|
||||
ba.print_exception(f'Error restoring state for {self}.')
|
||||
318
dist/ba_data/python/bastd/ui/settings/audio.py
vendored
Normal file
318
dist/ba_data/python/bastd/ui/settings/audio.py
vendored
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides audio settings UI."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class AudioSettingsWindow(ba.Window):
|
||||
"""Window for editing audio settings."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str = 'in_right',
|
||||
origin_widget: ba.Widget | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.popup import PopupMenu
|
||||
from bastd.ui.config import ConfigNumberEdit
|
||||
|
||||
music = ba.app.music
|
||||
|
||||
# If they provided an origin-widget, scale up from that.
|
||||
scale_origin: tuple[float, float] | None
|
||||
if origin_widget is not None:
|
||||
self._transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
|
||||
self._r = 'audioSettingsWindow'
|
||||
|
||||
spacing = 50.0
|
||||
width = 460.0
|
||||
height = 210.0
|
||||
|
||||
# Update: hard-coding head-relative audio to true now,
|
||||
# so not showing options.
|
||||
# show_vr_head_relative_audio = True if ba.app.vr_mode else False
|
||||
show_vr_head_relative_audio = False
|
||||
|
||||
if show_vr_head_relative_audio:
|
||||
height += 70
|
||||
|
||||
show_soundtracks = False
|
||||
if music.have_music_player():
|
||||
show_soundtracks = True
|
||||
height += spacing * 2.0
|
||||
|
||||
uiscale = ba.app.ui.uiscale
|
||||
base_scale = (
|
||||
2.05
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.6
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
)
|
||||
popup_menu_scale = base_scale * 1.2
|
||||
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(width, height),
|
||||
transition=transition,
|
||||
scale=base_scale,
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
stack_offset=(0, -20)
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else (0, 0),
|
||||
)
|
||||
)
|
||||
|
||||
self._back_button = back_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(35, height - 55),
|
||||
size=(120, 60),
|
||||
scale=0.8,
|
||||
text_scale=1.2,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
on_activate_call=self._back,
|
||||
autoselect=True,
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
v = height - 60
|
||||
v -= spacing * 1.0
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, height - 32),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.titleText'),
|
||||
color=ba.app.ui.title_color,
|
||||
maxwidth=180,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
ba.buttonwidget(
|
||||
edit=self._back_button,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
self._sound_volume_numedit = svne = ConfigNumberEdit(
|
||||
parent=self._root_widget,
|
||||
position=(40, v),
|
||||
xoffset=10,
|
||||
configkey='Sound Volume',
|
||||
displayname=ba.Lstr(resource=self._r + '.soundVolumeText'),
|
||||
minval=0.0,
|
||||
maxval=1.0,
|
||||
increment=0.1,
|
||||
)
|
||||
if ba.app.ui.use_toolbars:
|
||||
ba.widget(
|
||||
edit=svne.plusbutton,
|
||||
right_widget=ba.internal.get_special_widget('party_button'),
|
||||
)
|
||||
v -= spacing
|
||||
self._music_volume_numedit = ConfigNumberEdit(
|
||||
parent=self._root_widget,
|
||||
position=(40, v),
|
||||
xoffset=10,
|
||||
configkey='Music Volume',
|
||||
displayname=ba.Lstr(resource=self._r + '.musicVolumeText'),
|
||||
minval=0.0,
|
||||
maxval=1.0,
|
||||
increment=0.1,
|
||||
callback=music.music_volume_changed,
|
||||
changesound=False,
|
||||
)
|
||||
|
||||
v -= 0.5 * spacing
|
||||
|
||||
self._vr_head_relative_audio_button: ba.Widget | None
|
||||
if show_vr_head_relative_audio:
|
||||
v -= 40
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(40, v + 24),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.headRelativeVRAudioText'),
|
||||
color=(0.8, 0.8, 0.8),
|
||||
maxwidth=230,
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
popup = PopupMenu(
|
||||
parent=self._root_widget,
|
||||
position=(290, v),
|
||||
width=120,
|
||||
button_size=(135, 50),
|
||||
scale=popup_menu_scale,
|
||||
choices=['Auto', 'On', 'Off'],
|
||||
choices_display=[
|
||||
ba.Lstr(resource='autoText'),
|
||||
ba.Lstr(resource='onText'),
|
||||
ba.Lstr(resource='offText'),
|
||||
],
|
||||
current_choice=ba.app.config.resolve('VR Head Relative Audio'),
|
||||
on_value_change_call=self._set_vr_head_relative_audio,
|
||||
)
|
||||
self._vr_head_relative_audio_button = popup.get_button()
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, v - 11),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.headRelativeVRAudioInfoText'),
|
||||
scale=0.5,
|
||||
color=(0.7, 0.8, 0.7),
|
||||
maxwidth=400,
|
||||
flatness=1.0,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= 30
|
||||
else:
|
||||
self._vr_head_relative_audio_button = None
|
||||
|
||||
self._soundtrack_button: ba.Widget | None
|
||||
if show_soundtracks:
|
||||
v -= 1.2 * spacing
|
||||
self._soundtrack_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=((width - 310) / 2, v),
|
||||
size=(310, 50),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=self._r + '.soundtrackButtonText'),
|
||||
on_activate_call=self._do_soundtracks,
|
||||
)
|
||||
v -= spacing * 0.5
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, v),
|
||||
size=(width, 20),
|
||||
text=ba.Lstr(resource=self._r + '.soundtrackDescriptionText'),
|
||||
flatness=1.0,
|
||||
h_align='center',
|
||||
scale=0.5,
|
||||
color=(0.7, 0.8, 0.7, 1.0),
|
||||
maxwidth=400,
|
||||
)
|
||||
else:
|
||||
self._soundtrack_button = None
|
||||
|
||||
# Tweak a few navigation bits.
|
||||
try:
|
||||
ba.widget(edit=back_button, down_widget=svne.minusbutton)
|
||||
except Exception:
|
||||
ba.print_exception('Error wiring AudioSettingsWindow.')
|
||||
|
||||
self._restore_state()
|
||||
|
||||
def _set_vr_head_relative_audio(self, val: str) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg['VR Head Relative Audio'] = val
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _do_soundtracks(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.soundtrack import browser as stb
|
||||
|
||||
# We require disk access for soundtracks;
|
||||
# if we don't have it, request it.
|
||||
if not ba.internal.have_permission(ba.Permission.STORAGE):
|
||||
ba.playsound(ba.getsound('ding'))
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource='storagePermissionAccessText'),
|
||||
color=(0.5, 1, 0.5),
|
||||
)
|
||||
ba.timer(
|
||||
1.0,
|
||||
ba.Call(ba.internal.request_permission, ba.Permission.STORAGE),
|
||||
timetype=ba.TimeType.REAL,
|
||||
)
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
stb.SoundtrackBrowserWindow(
|
||||
origin_widget=self._soundtrack_button
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _back(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings import allsettings
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
ba.app.ui.set_main_menu_window(
|
||||
allsettings.AllSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._sound_volume_numedit.minusbutton:
|
||||
sel_name = 'SoundMinus'
|
||||
elif sel == self._sound_volume_numedit.plusbutton:
|
||||
sel_name = 'SoundPlus'
|
||||
elif sel == self._music_volume_numedit.minusbutton:
|
||||
sel_name = 'MusicMinus'
|
||||
elif sel == self._music_volume_numedit.plusbutton:
|
||||
sel_name = 'MusicPlus'
|
||||
elif sel == self._soundtrack_button:
|
||||
sel_name = 'Soundtrack'
|
||||
elif sel == self._back_button:
|
||||
sel_name = 'Back'
|
||||
elif sel == self._vr_head_relative_audio_button:
|
||||
sel_name = 'VRHeadRelative'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection \'{sel}\'')
|
||||
ba.app.ui.window_states[type(self)] = sel_name
|
||||
except Exception:
|
||||
ba.print_exception(f'Error saving state for {self.__class__}.')
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
sel_name = ba.app.ui.window_states.get(type(self))
|
||||
sel: ba.Widget | None
|
||||
if sel_name == 'SoundMinus':
|
||||
sel = self._sound_volume_numedit.minusbutton
|
||||
elif sel_name == 'SoundPlus':
|
||||
sel = self._sound_volume_numedit.plusbutton
|
||||
elif sel_name == 'MusicMinus':
|
||||
sel = self._music_volume_numedit.minusbutton
|
||||
elif sel_name == 'MusicPlus':
|
||||
sel = self._music_volume_numedit.plusbutton
|
||||
elif sel_name == 'VRHeadRelative':
|
||||
sel = self._vr_head_relative_audio_button
|
||||
elif sel_name == 'Soundtrack':
|
||||
sel = self._soundtrack_button
|
||||
elif sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
else:
|
||||
sel = self._back_button
|
||||
if sel:
|
||||
ba.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
except Exception:
|
||||
ba.print_exception(f'Error restoring state for {self.__class__}.')
|
||||
476
dist/ba_data/python/bastd/ui/settings/controls.py
vendored
Normal file
476
dist/ba_data/python/bastd/ui/settings/controls.py
vendored
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides a top level control settings window."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class ControlsSettingsWindow(ba.Window):
|
||||
"""Top level control settings window."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str = 'in_right',
|
||||
origin_widget: ba.Widget | None = None,
|
||||
):
|
||||
# FIXME: should tidy up here.
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui import popup as popup_ui
|
||||
|
||||
self._have_selected_child = False
|
||||
|
||||
scale_origin: tuple[float, float] | None
|
||||
|
||||
# If they provided an origin-widget, scale up from that.
|
||||
if origin_widget is not None:
|
||||
self._transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
|
||||
self._r = 'configControllersWindow'
|
||||
app = ba.app
|
||||
|
||||
# is_fire_tv = ba.internal.is_running_on_fire_tv()
|
||||
|
||||
spacing = 50.0
|
||||
button_width = 350.0
|
||||
width = 460.0
|
||||
height = 130.0
|
||||
|
||||
space_height = spacing * 0.3
|
||||
|
||||
# FIXME: should create vis settings in platform for these,
|
||||
# not hard code them here.
|
||||
|
||||
show_gamepads = False
|
||||
platform = app.platform
|
||||
subplatform = app.subplatform
|
||||
non_vr_windows = platform == 'windows' and (
|
||||
subplatform != 'oculus' or not app.vr_mode
|
||||
)
|
||||
if platform in ('linux', 'android', 'mac') or non_vr_windows:
|
||||
show_gamepads = True
|
||||
height += spacing
|
||||
|
||||
show_touch = False
|
||||
if ba.internal.have_touchscreen_input():
|
||||
show_touch = True
|
||||
height += spacing
|
||||
|
||||
show_space_1 = False
|
||||
if show_gamepads or show_touch:
|
||||
show_space_1 = True
|
||||
height += space_height
|
||||
|
||||
show_keyboard = False
|
||||
if (
|
||||
ba.internal.getinputdevice('Keyboard', '#1', doraise=False)
|
||||
is not None
|
||||
):
|
||||
show_keyboard = True
|
||||
height += spacing
|
||||
show_keyboard_p2 = False if app.vr_mode else show_keyboard
|
||||
if show_keyboard_p2:
|
||||
height += spacing
|
||||
|
||||
show_space_2 = False
|
||||
if show_keyboard:
|
||||
show_space_2 = True
|
||||
height += space_height
|
||||
|
||||
if bool(True):
|
||||
show_remote = True
|
||||
height += spacing
|
||||
else:
|
||||
show_remote = False
|
||||
|
||||
# On windows (outside of oculus/vr), show an option to disable xinput.
|
||||
show_xinput_toggle = False
|
||||
if platform == 'windows' and not app.vr_mode:
|
||||
show_xinput_toggle = True
|
||||
|
||||
# On mac builds, show an option to switch between generic and
|
||||
# made-for-iOS/Mac systems
|
||||
# (we can run into problems where devices register as one of each
|
||||
# type otherwise)..
|
||||
show_mac_controller_subsystem = False
|
||||
if platform == 'mac' and ba.internal.is_xcode_build():
|
||||
show_mac_controller_subsystem = True
|
||||
|
||||
if show_mac_controller_subsystem:
|
||||
height += spacing * 1.5
|
||||
|
||||
if show_xinput_toggle:
|
||||
height += spacing
|
||||
|
||||
uiscale = ba.app.ui.uiscale
|
||||
smallscale = 1.7 if show_keyboard else 2.2
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(width, height),
|
||||
transition=transition,
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
stack_offset=(
|
||||
(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0)
|
||||
),
|
||||
scale=(
|
||||
smallscale
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.5
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
)
|
||||
)
|
||||
self._back_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(35, height - 60),
|
||||
size=(140, 65),
|
||||
scale=0.8,
|
||||
text_scale=1.2,
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
on_activate_call=self._back,
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
# We need these vars to exist even if the buttons don't.
|
||||
self._gamepads_button: ba.Widget | None = None
|
||||
self._touch_button: ba.Widget | None = None
|
||||
self._keyboard_button: ba.Widget | None = None
|
||||
self._keyboard_2_button: ba.Widget | None = None
|
||||
self._idevices_button: ba.Widget | None = None
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, height - 49),
|
||||
size=(width, 25),
|
||||
text=ba.Lstr(resource=self._r + '.titleText'),
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
)
|
||||
ba.buttonwidget(
|
||||
edit=btn,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
v = height - 75
|
||||
v -= spacing
|
||||
|
||||
if show_touch:
|
||||
self._touch_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=((width - button_width) / 2, v),
|
||||
size=(button_width, 43),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=self._r + '.configureTouchText'),
|
||||
on_activate_call=self._do_touchscreen,
|
||||
)
|
||||
if ba.app.ui.use_toolbars:
|
||||
ba.widget(
|
||||
edit=btn,
|
||||
right_widget=ba.internal.get_special_widget('party_button'),
|
||||
)
|
||||
if not self._have_selected_child:
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._touch_button
|
||||
)
|
||||
ba.widget(
|
||||
edit=self._back_button, down_widget=self._touch_button
|
||||
)
|
||||
self._have_selected_child = True
|
||||
v -= spacing
|
||||
|
||||
if show_gamepads:
|
||||
self._gamepads_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=((width - button_width) / 2 - 7, v),
|
||||
size=(button_width, 43),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=self._r + '.configureControllersText'),
|
||||
on_activate_call=self._do_gamepads,
|
||||
)
|
||||
if ba.app.ui.use_toolbars:
|
||||
ba.widget(
|
||||
edit=btn,
|
||||
right_widget=ba.internal.get_special_widget('party_button'),
|
||||
)
|
||||
if not self._have_selected_child:
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._gamepads_button
|
||||
)
|
||||
ba.widget(
|
||||
edit=self._back_button, down_widget=self._gamepads_button
|
||||
)
|
||||
self._have_selected_child = True
|
||||
v -= spacing
|
||||
else:
|
||||
self._gamepads_button = None
|
||||
|
||||
if show_space_1:
|
||||
v -= space_height
|
||||
|
||||
if show_keyboard:
|
||||
self._keyboard_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=((width - button_width) / 2 + 5, v),
|
||||
size=(button_width, 43),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=self._r + '.configureKeyboardText'),
|
||||
on_activate_call=self._config_keyboard,
|
||||
)
|
||||
if ba.app.ui.use_toolbars:
|
||||
ba.widget(
|
||||
edit=btn,
|
||||
right_widget=ba.internal.get_special_widget('party_button'),
|
||||
)
|
||||
if not self._have_selected_child:
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._keyboard_button
|
||||
)
|
||||
ba.widget(
|
||||
edit=self._back_button, down_widget=self._keyboard_button
|
||||
)
|
||||
self._have_selected_child = True
|
||||
v -= spacing
|
||||
if show_keyboard_p2:
|
||||
self._keyboard_2_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=((width - button_width) / 2 - 3, v),
|
||||
size=(button_width, 43),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=self._r + '.configureKeyboard2Text'),
|
||||
on_activate_call=self._config_keyboard2,
|
||||
)
|
||||
v -= spacing
|
||||
if show_space_2:
|
||||
v -= space_height
|
||||
if show_remote:
|
||||
self._idevices_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=((width - button_width) / 2 - 5, v),
|
||||
size=(button_width, 43),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=self._r + '.configureMobileText'),
|
||||
on_activate_call=self._do_mobile_devices,
|
||||
)
|
||||
if ba.app.ui.use_toolbars:
|
||||
ba.widget(
|
||||
edit=btn,
|
||||
right_widget=ba.internal.get_special_widget('party_button'),
|
||||
)
|
||||
if not self._have_selected_child:
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._idevices_button
|
||||
)
|
||||
ba.widget(
|
||||
edit=self._back_button, down_widget=self._idevices_button
|
||||
)
|
||||
self._have_selected_child = True
|
||||
v -= spacing
|
||||
|
||||
if show_xinput_toggle:
|
||||
|
||||
def do_toggle(value: bool) -> None:
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
|
||||
color=(1, 1, 0),
|
||||
)
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
ba.internal.set_low_level_config_value(
|
||||
'enablexinput', not value
|
||||
)
|
||||
|
||||
ba.checkboxwidget(
|
||||
parent=self._root_widget,
|
||||
position=(100, v + 3),
|
||||
size=(120, 30),
|
||||
value=(
|
||||
not ba.internal.get_low_level_config_value(
|
||||
'enablexinput', 1
|
||||
)
|
||||
),
|
||||
maxwidth=200,
|
||||
on_value_change_call=do_toggle,
|
||||
text=ba.Lstr(resource='disableXInputText'),
|
||||
autoselect=True,
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, v - 5),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource='disableXInputDescriptionText'),
|
||||
scale=0.5,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
color=ba.app.ui.infotextcolor,
|
||||
maxwidth=width * 0.8,
|
||||
)
|
||||
v -= spacing
|
||||
if show_mac_controller_subsystem:
|
||||
popup_ui.PopupMenu(
|
||||
parent=self._root_widget,
|
||||
position=(260, v - 10),
|
||||
width=160,
|
||||
button_size=(150, 50),
|
||||
scale=1.5,
|
||||
choices=['Classic', 'MFi', 'Both'],
|
||||
choices_display=[
|
||||
ba.Lstr(resource='macControllerSubsystemClassicText'),
|
||||
ba.Lstr(resource='macControllerSubsystemMFiText'),
|
||||
ba.Lstr(resource='macControllerSubsystemBothText'),
|
||||
],
|
||||
current_choice=ba.app.config.resolve(
|
||||
'Mac Controller Subsystem'
|
||||
),
|
||||
on_value_change_call=self._set_mac_controller_subsystem,
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(245, v + 13),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource='macControllerSubsystemTitleText'),
|
||||
scale=1.0,
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
color=ba.app.ui.infotextcolor,
|
||||
maxwidth=180,
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, v - 20),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource='macControllerSubsystemDescriptionText'),
|
||||
scale=0.5,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
color=ba.app.ui.infotextcolor,
|
||||
maxwidth=width * 0.8,
|
||||
)
|
||||
v -= spacing * 1.5
|
||||
self._restore_state()
|
||||
|
||||
def _set_mac_controller_subsystem(self, val: str) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg['Mac Controller Subsystem'] = val
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _config_keyboard(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.keyboard import ConfigKeyboardWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
ConfigKeyboardWindow(
|
||||
ba.internal.getinputdevice('Keyboard', '#1')
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _config_keyboard2(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.keyboard import ConfigKeyboardWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
ConfigKeyboardWindow(
|
||||
ba.internal.getinputdevice('Keyboard', '#2')
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _do_mobile_devices(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.remoteapp import RemoteAppSettingsWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
RemoteAppSettingsWindow().get_root_widget()
|
||||
)
|
||||
|
||||
def _do_gamepads(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.gamepadselect import GamepadSelectWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(GamepadSelectWindow().get_root_widget())
|
||||
|
||||
def _do_touchscreen(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.touchscreen import TouchscreenSettingsWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
TouchscreenSettingsWindow().get_root_widget()
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._gamepads_button:
|
||||
sel_name = 'GamePads'
|
||||
elif sel == self._touch_button:
|
||||
sel_name = 'Touch'
|
||||
elif sel == self._keyboard_button:
|
||||
sel_name = 'Keyboard'
|
||||
elif sel == self._keyboard_2_button:
|
||||
sel_name = 'Keyboard2'
|
||||
elif sel == self._idevices_button:
|
||||
sel_name = 'iDevices'
|
||||
else:
|
||||
sel_name = 'Back'
|
||||
ba.app.ui.window_states[type(self)] = sel_name
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
sel_name = ba.app.ui.window_states.get(type(self))
|
||||
if sel_name == 'GamePads':
|
||||
sel = self._gamepads_button
|
||||
elif sel_name == 'Touch':
|
||||
sel = self._touch_button
|
||||
elif sel_name == 'Keyboard':
|
||||
sel = self._keyboard_button
|
||||
elif sel_name == 'Keyboard2':
|
||||
sel = self._keyboard_2_button
|
||||
elif sel_name == 'iDevices':
|
||||
sel = self._idevices_button
|
||||
elif sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
else:
|
||||
sel = (
|
||||
self._gamepads_button
|
||||
if self._gamepads_button is not None
|
||||
else self._back_button
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
|
||||
def _back(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.allsettings import AllSettingsWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
ba.app.ui.set_main_menu_window(
|
||||
AllSettingsWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
973
dist/ba_data/python/bastd/ui/settings/gamepad.py
vendored
Normal file
973
dist/ba_data/python/bastd/ui/settings/gamepad.py
vendored
Normal file
|
|
@ -0,0 +1,973 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Settings UI functionality related to gamepads."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
class GamepadSettingsWindow(ba.Window):
|
||||
"""Window for configuring a gamepad."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
gamepad: ba.InputDevice,
|
||||
is_main_menu: bool = True,
|
||||
transition: str = 'in_right',
|
||||
transition_out: str = 'out_right',
|
||||
settings: dict | None = None,
|
||||
):
|
||||
self._input = gamepad
|
||||
|
||||
# If our input-device went away, just return an empty zombie.
|
||||
if not self._input:
|
||||
return
|
||||
|
||||
self._name = self._input.name
|
||||
|
||||
self._r = 'configGamepadWindow'
|
||||
self._settings = settings
|
||||
self._transition_out = transition_out
|
||||
|
||||
# We're a secondary gamepad if supplied with settings.
|
||||
self._is_secondary = settings is not None
|
||||
self._ext = '_B' if self._is_secondary else ''
|
||||
self._is_main_menu = is_main_menu
|
||||
self._displayname = self._name
|
||||
self._width = 700 if self._is_secondary else 730
|
||||
self._height = 440 if self._is_secondary else 450
|
||||
self._spacing = 40
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
scale=(
|
||||
1.63
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.35
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(-20, -16)
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else (0, 0),
|
||||
transition=transition,
|
||||
)
|
||||
)
|
||||
|
||||
# Don't ask to config joysticks while we're in here.
|
||||
self._rebuild_ui()
|
||||
|
||||
def _rebuild_ui(self) -> None:
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-locals
|
||||
from ba.internal import get_device_value
|
||||
|
||||
# Clear existing UI.
|
||||
for widget in self._root_widget.get_children():
|
||||
widget.delete()
|
||||
|
||||
self._textwidgets: dict[str, ba.Widget] = {}
|
||||
|
||||
# If we were supplied with settings, we're a secondary joystick and
|
||||
# just operate on that. in the other (normal) case we make our own.
|
||||
if not self._is_secondary:
|
||||
|
||||
# Fill our temp config with present values (for our primary and
|
||||
# secondary controls).
|
||||
self._settings = {}
|
||||
for skey in [
|
||||
'buttonJump',
|
||||
'buttonJump_B',
|
||||
'buttonPunch',
|
||||
'buttonPunch_B',
|
||||
'buttonBomb',
|
||||
'buttonBomb_B',
|
||||
'buttonPickUp',
|
||||
'buttonPickUp_B',
|
||||
'buttonStart',
|
||||
'buttonStart_B',
|
||||
'buttonStart2',
|
||||
'buttonStart2_B',
|
||||
'buttonUp',
|
||||
'buttonUp_B',
|
||||
'buttonDown',
|
||||
'buttonDown_B',
|
||||
'buttonLeft',
|
||||
'buttonLeft_B',
|
||||
'buttonRight',
|
||||
'buttonRight_B',
|
||||
'buttonRun1',
|
||||
'buttonRun1_B',
|
||||
'buttonRun2',
|
||||
'buttonRun2_B',
|
||||
'triggerRun1',
|
||||
'triggerRun1_B',
|
||||
'triggerRun2',
|
||||
'triggerRun2_B',
|
||||
'buttonIgnored',
|
||||
'buttonIgnored_B',
|
||||
'buttonIgnored2',
|
||||
'buttonIgnored2_B',
|
||||
'buttonIgnored3',
|
||||
'buttonIgnored3_B',
|
||||
'buttonIgnored4',
|
||||
'buttonIgnored4_B',
|
||||
'buttonVRReorient',
|
||||
'buttonVRReorient_B',
|
||||
'analogStickDeadZone',
|
||||
'analogStickDeadZone_B',
|
||||
'dpad',
|
||||
'dpad_B',
|
||||
'unassignedButtonsRun',
|
||||
'unassignedButtonsRun_B',
|
||||
'startButtonActivatesDefaultWidget',
|
||||
'startButtonActivatesDefaultWidget_B',
|
||||
'uiOnly',
|
||||
'uiOnly_B',
|
||||
'ignoreCompletely',
|
||||
'ignoreCompletely_B',
|
||||
'autoRecalibrateAnalogStick',
|
||||
'autoRecalibrateAnalogStick_B',
|
||||
'analogStickLR',
|
||||
'analogStickLR_B',
|
||||
'analogStickUD',
|
||||
'analogStickUD_B',
|
||||
'enableSecondary',
|
||||
]:
|
||||
val = get_device_value(self._input, skey)
|
||||
if val != -1:
|
||||
self._settings[skey] = val
|
||||
|
||||
back_button: ba.Widget | None
|
||||
|
||||
if self._is_secondary:
|
||||
back_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width - 180, self._height - 65),
|
||||
autoselect=True,
|
||||
size=(160, 60),
|
||||
label=ba.Lstr(resource='doneText'),
|
||||
scale=0.9,
|
||||
on_activate_call=self._save,
|
||||
)
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget,
|
||||
start_button=back_button,
|
||||
on_cancel_call=back_button.activate,
|
||||
)
|
||||
cancel_button = None
|
||||
else:
|
||||
cancel_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(51, self._height - 65),
|
||||
autoselect=True,
|
||||
size=(160, 60),
|
||||
label=ba.Lstr(resource='cancelText'),
|
||||
scale=0.9,
|
||||
on_activate_call=self._cancel,
|
||||
)
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, cancel_button=cancel_button
|
||||
)
|
||||
|
||||
save_button: ba.Widget | None
|
||||
if not self._is_secondary:
|
||||
save_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._width - (165 if self._is_secondary else 195),
|
||||
self._height - 65,
|
||||
),
|
||||
size=((160 if self._is_secondary else 180), 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='doneText')
|
||||
if self._is_secondary
|
||||
else ba.Lstr(resource='saveText'),
|
||||
scale=0.9,
|
||||
on_activate_call=self._save,
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, start_button=save_button)
|
||||
else:
|
||||
save_button = None
|
||||
|
||||
if not self._is_secondary:
|
||||
v = self._height - 59
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, v + 5),
|
||||
size=(self._width, 25),
|
||||
text=ba.Lstr(resource=self._r + '.titleText'),
|
||||
color=ba.app.ui.title_color,
|
||||
maxwidth=310,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= 48
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, v + 3),
|
||||
size=(self._width, 25),
|
||||
text=self._name,
|
||||
color=ba.app.ui.infotextcolor,
|
||||
maxwidth=self._width * 0.9,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= self._spacing * 1
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(50, v + 10),
|
||||
size=(self._width - 100, 30),
|
||||
text=ba.Lstr(resource=self._r + '.appliesToAllText'),
|
||||
maxwidth=330,
|
||||
scale=0.65,
|
||||
color=(0.5, 0.6, 0.5, 1.0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= 70
|
||||
self._enable_check_box = None
|
||||
else:
|
||||
v = self._height - 49
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, v + 5),
|
||||
size=(self._width, 25),
|
||||
text=ba.Lstr(resource=self._r + '.secondaryText'),
|
||||
color=ba.app.ui.title_color,
|
||||
maxwidth=300,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= self._spacing * 1
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(50, v + 10),
|
||||
size=(self._width - 100, 30),
|
||||
text=ba.Lstr(resource=self._r + '.secondHalfText'),
|
||||
maxwidth=300,
|
||||
scale=0.65,
|
||||
color=(0.6, 0.8, 0.6, 1.0),
|
||||
h_align='center',
|
||||
)
|
||||
self._enable_check_box = ba.checkboxwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5 - 80, v - 73),
|
||||
value=self.get_enable_secondary_value(),
|
||||
autoselect=True,
|
||||
on_value_change_call=self._enable_check_box_changed,
|
||||
size=(200, 30),
|
||||
text=ba.Lstr(resource=self._r + '.secondaryEnableText'),
|
||||
scale=1.2,
|
||||
)
|
||||
v = self._height - 205
|
||||
|
||||
h_offs = 160
|
||||
dist = 70
|
||||
d_color = (0.4, 0.4, 0.8)
|
||||
sclx = 1.2
|
||||
scly = 0.98
|
||||
dpm = ba.Lstr(resource=self._r + '.pressAnyButtonOrDpadText')
|
||||
dpm2 = ba.Lstr(resource=self._r + '.ifNothingHappensTryAnalogText')
|
||||
self._capture_button(
|
||||
pos=(h_offs, v + scly * dist),
|
||||
color=d_color,
|
||||
button='buttonUp' + self._ext,
|
||||
texture=ba.gettexture('upButton'),
|
||||
scale=1.0,
|
||||
message=dpm,
|
||||
message2=dpm2,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs - sclx * dist, v),
|
||||
color=d_color,
|
||||
button='buttonLeft' + self._ext,
|
||||
texture=ba.gettexture('leftButton'),
|
||||
scale=1.0,
|
||||
message=dpm,
|
||||
message2=dpm2,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs + sclx * dist, v),
|
||||
color=d_color,
|
||||
button='buttonRight' + self._ext,
|
||||
texture=ba.gettexture('rightButton'),
|
||||
scale=1.0,
|
||||
message=dpm,
|
||||
message2=dpm2,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs, v - scly * dist),
|
||||
color=d_color,
|
||||
button='buttonDown' + self._ext,
|
||||
texture=ba.gettexture('downButton'),
|
||||
scale=1.0,
|
||||
message=dpm,
|
||||
message2=dpm2,
|
||||
)
|
||||
|
||||
dpm3 = ba.Lstr(resource=self._r + '.ifNothingHappensTryDpadText')
|
||||
self._capture_button(
|
||||
pos=(h_offs + 130, v - 125),
|
||||
color=(0.4, 0.4, 0.6),
|
||||
button='analogStickLR' + self._ext,
|
||||
maxwidth=140,
|
||||
texture=ba.gettexture('analogStick'),
|
||||
scale=1.2,
|
||||
message=ba.Lstr(resource=self._r + '.pressLeftRightText'),
|
||||
message2=dpm3,
|
||||
)
|
||||
|
||||
self._capture_button(
|
||||
pos=(self._width * 0.5, v),
|
||||
color=(0.4, 0.4, 0.6),
|
||||
button='buttonStart' + self._ext,
|
||||
texture=ba.gettexture('startButton'),
|
||||
scale=0.7,
|
||||
)
|
||||
|
||||
h_offs = self._width - 160
|
||||
|
||||
self._capture_button(
|
||||
pos=(h_offs, v + scly * dist),
|
||||
color=(0.6, 0.4, 0.8),
|
||||
button='buttonPickUp' + self._ext,
|
||||
texture=ba.gettexture('buttonPickUp'),
|
||||
scale=1.0,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs - sclx * dist, v),
|
||||
color=(0.7, 0.5, 0.1),
|
||||
button='buttonPunch' + self._ext,
|
||||
texture=ba.gettexture('buttonPunch'),
|
||||
scale=1.0,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs + sclx * dist, v),
|
||||
color=(0.5, 0.2, 0.1),
|
||||
button='buttonBomb' + self._ext,
|
||||
texture=ba.gettexture('buttonBomb'),
|
||||
scale=1.0,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs, v - scly * dist),
|
||||
color=(0.2, 0.5, 0.2),
|
||||
button='buttonJump' + self._ext,
|
||||
texture=ba.gettexture('buttonJump'),
|
||||
scale=1.0,
|
||||
)
|
||||
|
||||
self._advanced_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=self._r + '.advancedText'),
|
||||
text_scale=0.9,
|
||||
color=(0.45, 0.4, 0.5),
|
||||
textcolor=(0.65, 0.6, 0.7),
|
||||
position=(self._width - 300, 30),
|
||||
size=(130, 40),
|
||||
on_activate_call=self._do_advanced,
|
||||
)
|
||||
|
||||
try:
|
||||
if cancel_button is not None and save_button is not None:
|
||||
ba.widget(edit=cancel_button, right_widget=save_button)
|
||||
ba.widget(edit=save_button, left_widget=cancel_button)
|
||||
except Exception:
|
||||
ba.print_exception('Error wiring up gamepad config window.')
|
||||
|
||||
def get_r(self) -> str:
|
||||
"""(internal)"""
|
||||
return self._r
|
||||
|
||||
def get_advanced_button(self) -> ba.Widget:
|
||||
"""(internal)"""
|
||||
return self._advanced_button
|
||||
|
||||
def get_is_secondary(self) -> bool:
|
||||
"""(internal)"""
|
||||
return self._is_secondary
|
||||
|
||||
def get_settings(self) -> dict[str, Any]:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
return self._settings
|
||||
|
||||
def get_ext(self) -> str:
|
||||
"""(internal)"""
|
||||
return self._ext
|
||||
|
||||
def get_input(self) -> ba.InputDevice:
|
||||
"""(internal)"""
|
||||
return self._input
|
||||
|
||||
def _do_advanced(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings import gamepadadvanced
|
||||
|
||||
gamepadadvanced.GamepadAdvancedSettingsWindow(self)
|
||||
|
||||
def _enable_check_box_changed(self, value: bool) -> None:
|
||||
assert self._settings is not None
|
||||
if value:
|
||||
self._settings['enableSecondary'] = 1
|
||||
else:
|
||||
# Just clear since this is default.
|
||||
if 'enableSecondary' in self._settings:
|
||||
del self._settings['enableSecondary']
|
||||
|
||||
def get_unassigned_buttons_run_value(self) -> bool:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
return self._settings.get('unassignedButtonsRun', True)
|
||||
|
||||
def set_unassigned_buttons_run_value(self, value: bool) -> None:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
if value:
|
||||
if 'unassignedButtonsRun' in self._settings:
|
||||
|
||||
# Clear since this is default.
|
||||
del self._settings['unassignedButtonsRun']
|
||||
return
|
||||
self._settings['unassignedButtonsRun'] = False
|
||||
|
||||
def get_start_button_activates_default_widget_value(self) -> bool:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
return self._settings.get('startButtonActivatesDefaultWidget', True)
|
||||
|
||||
def set_start_button_activates_default_widget_value(
|
||||
self, value: bool
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
if value:
|
||||
if 'startButtonActivatesDefaultWidget' in self._settings:
|
||||
|
||||
# Clear since this is default.
|
||||
del self._settings['startButtonActivatesDefaultWidget']
|
||||
return
|
||||
self._settings['startButtonActivatesDefaultWidget'] = False
|
||||
|
||||
def get_ui_only_value(self) -> bool:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
return self._settings.get('uiOnly', False)
|
||||
|
||||
def set_ui_only_value(self, value: bool) -> None:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
if not value:
|
||||
if 'uiOnly' in self._settings:
|
||||
|
||||
# Clear since this is default.
|
||||
del self._settings['uiOnly']
|
||||
return
|
||||
self._settings['uiOnly'] = True
|
||||
|
||||
def get_ignore_completely_value(self) -> bool:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
return self._settings.get('ignoreCompletely', False)
|
||||
|
||||
def set_ignore_completely_value(self, value: bool) -> None:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
if not value:
|
||||
if 'ignoreCompletely' in self._settings:
|
||||
|
||||
# Clear since this is default.
|
||||
del self._settings['ignoreCompletely']
|
||||
return
|
||||
self._settings['ignoreCompletely'] = True
|
||||
|
||||
def get_auto_recalibrate_analog_stick_value(self) -> bool:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
return self._settings.get('autoRecalibrateAnalogStick', False)
|
||||
|
||||
def set_auto_recalibrate_analog_stick_value(self, value: bool) -> None:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
if not value:
|
||||
if 'autoRecalibrateAnalogStick' in self._settings:
|
||||
|
||||
# Clear since this is default.
|
||||
del self._settings['autoRecalibrateAnalogStick']
|
||||
else:
|
||||
self._settings['autoRecalibrateAnalogStick'] = True
|
||||
|
||||
def get_enable_secondary_value(self) -> bool:
|
||||
"""(internal)"""
|
||||
assert self._settings is not None
|
||||
if not self._is_secondary:
|
||||
raise Exception('enable value only applies to secondary editor')
|
||||
return self._settings.get('enableSecondary', False)
|
||||
|
||||
def show_secondary_editor(self) -> None:
|
||||
"""(internal)"""
|
||||
GamepadSettingsWindow(
|
||||
self._input,
|
||||
is_main_menu=False,
|
||||
settings=self._settings,
|
||||
transition='in_scale',
|
||||
transition_out='out_scale',
|
||||
)
|
||||
|
||||
def get_control_value_name(self, control: str) -> str | ba.Lstr:
|
||||
"""(internal)"""
|
||||
# pylint: disable=too-many-return-statements
|
||||
assert self._settings is not None
|
||||
if control == 'analogStickLR' + self._ext:
|
||||
|
||||
# This actually shows both LR and UD.
|
||||
sval1 = (
|
||||
self._settings['analogStickLR' + self._ext]
|
||||
if 'analogStickLR' + self._ext in self._settings
|
||||
else 5
|
||||
if self._is_secondary
|
||||
else 1
|
||||
)
|
||||
sval2 = (
|
||||
self._settings['analogStickUD' + self._ext]
|
||||
if 'analogStickUD' + self._ext in self._settings
|
||||
else 6
|
||||
if self._is_secondary
|
||||
else 2
|
||||
)
|
||||
return (
|
||||
self._input.get_axis_name(sval1)
|
||||
+ ' / '
|
||||
+ self._input.get_axis_name(sval2)
|
||||
)
|
||||
|
||||
# If they're looking for triggers.
|
||||
if control in ['triggerRun1' + self._ext, 'triggerRun2' + self._ext]:
|
||||
if control in self._settings:
|
||||
return self._input.get_axis_name(self._settings[control])
|
||||
return ba.Lstr(resource=self._r + '.unsetText')
|
||||
|
||||
# Dead-zone.
|
||||
if control == 'analogStickDeadZone' + self._ext:
|
||||
if control in self._settings:
|
||||
return str(self._settings[control])
|
||||
return str(1.0)
|
||||
|
||||
# For dpad buttons: show individual buttons if any are set.
|
||||
# Otherwise show whichever dpad is set (defaulting to 1).
|
||||
dpad_buttons = [
|
||||
'buttonLeft' + self._ext,
|
||||
'buttonRight' + self._ext,
|
||||
'buttonUp' + self._ext,
|
||||
'buttonDown' + self._ext,
|
||||
]
|
||||
if control in dpad_buttons:
|
||||
|
||||
# If *any* dpad buttons are assigned, show only button assignments.
|
||||
if any(b in self._settings for b in dpad_buttons):
|
||||
if control in self._settings:
|
||||
return self._input.get_button_name(self._settings[control])
|
||||
return ba.Lstr(resource=self._r + '.unsetText')
|
||||
|
||||
# No dpad buttons - show the dpad number for all 4.
|
||||
return ba.Lstr(
|
||||
value='${A} ${B}',
|
||||
subs=[
|
||||
('${A}', ba.Lstr(resource=self._r + '.dpadText')),
|
||||
(
|
||||
'${B}',
|
||||
str(
|
||||
self._settings['dpad' + self._ext]
|
||||
if 'dpad' + self._ext in self._settings
|
||||
else 2
|
||||
if self._is_secondary
|
||||
else 1
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# other buttons..
|
||||
if control in self._settings:
|
||||
return self._input.get_button_name(self._settings[control])
|
||||
return ba.Lstr(resource=self._r + '.unsetText')
|
||||
|
||||
def _gamepad_event(
|
||||
self,
|
||||
control: str,
|
||||
event: dict[str, Any],
|
||||
dialog: AwaitGamepadInputWindow,
|
||||
) -> None:
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
assert self._settings is not None
|
||||
ext = self._ext
|
||||
|
||||
# For our dpad-buttons we're looking for either a button-press or a
|
||||
# hat-switch press.
|
||||
if control in [
|
||||
'buttonUp' + ext,
|
||||
'buttonLeft' + ext,
|
||||
'buttonDown' + ext,
|
||||
'buttonRight' + ext,
|
||||
]:
|
||||
if event['type'] in ['BUTTONDOWN', 'HATMOTION']:
|
||||
|
||||
# If its a button-down.
|
||||
if event['type'] == 'BUTTONDOWN':
|
||||
value = event['button']
|
||||
self._settings[control] = value
|
||||
|
||||
# If its a dpad.
|
||||
elif event['type'] == 'HATMOTION':
|
||||
# clear out any set dir-buttons
|
||||
for btn in [
|
||||
'buttonUp' + ext,
|
||||
'buttonLeft' + ext,
|
||||
'buttonRight' + ext,
|
||||
'buttonDown' + ext,
|
||||
]:
|
||||
if btn in self._settings:
|
||||
del self._settings[btn]
|
||||
if event['hat'] == (2 if self._is_secondary else 1):
|
||||
|
||||
# Exclude value in default case.
|
||||
if 'dpad' + ext in self._settings:
|
||||
del self._settings['dpad' + ext]
|
||||
else:
|
||||
self._settings['dpad' + ext] = event['hat']
|
||||
|
||||
# Update the 4 dpad button txt widgets.
|
||||
ba.textwidget(
|
||||
edit=self._textwidgets['buttonUp' + ext],
|
||||
text=self.get_control_value_name('buttonUp' + ext),
|
||||
)
|
||||
ba.textwidget(
|
||||
edit=self._textwidgets['buttonLeft' + ext],
|
||||
text=self.get_control_value_name('buttonLeft' + ext),
|
||||
)
|
||||
ba.textwidget(
|
||||
edit=self._textwidgets['buttonRight' + ext],
|
||||
text=self.get_control_value_name('buttonRight' + ext),
|
||||
)
|
||||
ba.textwidget(
|
||||
edit=self._textwidgets['buttonDown' + ext],
|
||||
text=self.get_control_value_name('buttonDown' + ext),
|
||||
)
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
dialog.die()
|
||||
|
||||
elif control == 'analogStickLR' + ext:
|
||||
if event['type'] == 'AXISMOTION':
|
||||
|
||||
# Ignore small values or else we might get triggered by noise.
|
||||
if abs(event['value']) > 0.5:
|
||||
axis = event['axis']
|
||||
if axis == (5 if self._is_secondary else 1):
|
||||
|
||||
# Exclude value in default case.
|
||||
if 'analogStickLR' + ext in self._settings:
|
||||
del self._settings['analogStickLR' + ext]
|
||||
else:
|
||||
self._settings['analogStickLR' + ext] = axis
|
||||
ba.textwidget(
|
||||
edit=self._textwidgets['analogStickLR' + ext],
|
||||
text=self.get_control_value_name('analogStickLR' + ext),
|
||||
)
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
dialog.die()
|
||||
|
||||
# Now launch the up/down listener.
|
||||
AwaitGamepadInputWindow(
|
||||
self._input,
|
||||
'analogStickUD' + ext,
|
||||
self._gamepad_event,
|
||||
ba.Lstr(resource=self._r + '.pressUpDownText'),
|
||||
)
|
||||
|
||||
elif control == 'analogStickUD' + ext:
|
||||
if event['type'] == 'AXISMOTION':
|
||||
|
||||
# Ignore small values or else we might get triggered by noise.
|
||||
if abs(event['value']) > 0.5:
|
||||
axis = event['axis']
|
||||
|
||||
# Ignore our LR axis.
|
||||
if 'analogStickLR' + ext in self._settings:
|
||||
lr_axis = self._settings['analogStickLR' + ext]
|
||||
else:
|
||||
lr_axis = 5 if self._is_secondary else 1
|
||||
if axis != lr_axis:
|
||||
if axis == (6 if self._is_secondary else 2):
|
||||
|
||||
# Exclude value in default case.
|
||||
if 'analogStickUD' + ext in self._settings:
|
||||
del self._settings['analogStickUD' + ext]
|
||||
else:
|
||||
self._settings['analogStickUD' + ext] = axis
|
||||
ba.textwidget(
|
||||
edit=self._textwidgets['analogStickLR' + ext],
|
||||
text=self.get_control_value_name(
|
||||
'analogStickLR' + ext
|
||||
),
|
||||
)
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
dialog.die()
|
||||
else:
|
||||
# For other buttons we just want a button-press.
|
||||
if event['type'] == 'BUTTONDOWN':
|
||||
value = event['button']
|
||||
self._settings[control] = value
|
||||
|
||||
# Update the button's text widget.
|
||||
ba.textwidget(
|
||||
edit=self._textwidgets[control],
|
||||
text=self.get_control_value_name(control),
|
||||
)
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
dialog.die()
|
||||
|
||||
def _capture_button(
|
||||
self,
|
||||
pos: tuple[float, float],
|
||||
color: tuple[float, float, float],
|
||||
texture: ba.Texture,
|
||||
button: str,
|
||||
scale: float = 1.0,
|
||||
message: ba.Lstr | None = None,
|
||||
message2: ba.Lstr | None = None,
|
||||
maxwidth: float = 80.0,
|
||||
) -> ba.Widget:
|
||||
if message is None:
|
||||
message = ba.Lstr(resource=self._r + '.pressAnyButtonText')
|
||||
base_size = 79
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
pos[0] - base_size * 0.5 * scale,
|
||||
pos[1] - base_size * 0.5 * scale,
|
||||
),
|
||||
autoselect=True,
|
||||
size=(base_size * scale, base_size * scale),
|
||||
texture=texture,
|
||||
label='',
|
||||
color=color,
|
||||
)
|
||||
|
||||
# Make this in a timer so that it shows up on top of all other buttons.
|
||||
|
||||
def doit() -> None:
|
||||
uiscale = 0.9 * scale
|
||||
txt = ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(pos[0] + 0.0 * scale, pos[1] - 58.0 * scale),
|
||||
color=(1, 1, 1, 0.3),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=uiscale,
|
||||
text=self.get_control_value_name(button),
|
||||
maxwidth=maxwidth,
|
||||
)
|
||||
self._textwidgets[button] = txt
|
||||
ba.buttonwidget(
|
||||
edit=btn,
|
||||
on_activate_call=ba.Call(
|
||||
AwaitGamepadInputWindow,
|
||||
self._input,
|
||||
button,
|
||||
self._gamepad_event,
|
||||
message,
|
||||
message2,
|
||||
),
|
||||
)
|
||||
|
||||
ba.timer(0, doit, timetype=ba.TimeType.REAL)
|
||||
return btn
|
||||
|
||||
def _cancel(self) -> None:
|
||||
from bastd.ui.settings.controls import ControlsSettingsWindow
|
||||
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
if self._is_main_menu:
|
||||
ba.app.ui.set_main_menu_window(
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
|
||||
def _save(self) -> None:
|
||||
from ba.internal import (
|
||||
master_server_post,
|
||||
get_input_device_config,
|
||||
get_input_map_hash,
|
||||
should_submit_debug_info,
|
||||
)
|
||||
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
|
||||
# If we're a secondary editor we just go away (we were editing our
|
||||
# parent's settings dict).
|
||||
if self._is_secondary:
|
||||
return
|
||||
|
||||
assert self._settings is not None
|
||||
if self._input:
|
||||
dst = get_input_device_config(self._input, default=True)
|
||||
dst2: dict[str, Any] = dst[0][dst[1]]
|
||||
dst2.clear()
|
||||
|
||||
# Store any values that aren't -1.
|
||||
for key, val in list(self._settings.items()):
|
||||
if val != -1:
|
||||
dst2[key] = val
|
||||
|
||||
# If we're allowed to phone home, send this config so we can
|
||||
# generate more defaults in the future.
|
||||
inputhash = get_input_map_hash(self._input)
|
||||
if should_submit_debug_info():
|
||||
master_server_post(
|
||||
'controllerConfig',
|
||||
{
|
||||
'ua': ba.app.user_agent_string,
|
||||
'b': ba.app.build_number,
|
||||
'name': self._name,
|
||||
'inputMapHash': inputhash,
|
||||
'config': dst2,
|
||||
'v': 2,
|
||||
},
|
||||
)
|
||||
ba.app.config.apply_and_commit()
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
else:
|
||||
ba.playsound(ba.getsound('error'))
|
||||
|
||||
if self._is_main_menu:
|
||||
from bastd.ui.settings.controls import ControlsSettingsWindow
|
||||
|
||||
ba.app.ui.set_main_menu_window(
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
|
||||
|
||||
class AwaitGamepadInputWindow(ba.Window):
|
||||
"""Window for capturing a gamepad button press."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
gamepad: ba.InputDevice,
|
||||
button: str,
|
||||
callback: Callable[[str, dict[str, Any], AwaitGamepadInputWindow], Any],
|
||||
message: ba.Lstr | None = None,
|
||||
message2: ba.Lstr | None = None,
|
||||
):
|
||||
if message is None:
|
||||
print('AwaitGamepadInputWindow message is None!')
|
||||
# Shouldn't get here.
|
||||
message = ba.Lstr(value='Press any button...')
|
||||
self._callback = callback
|
||||
self._input = gamepad
|
||||
self._capture_button = button
|
||||
width = 400
|
||||
height = 150
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
scale=(
|
||||
2.0
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.9
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
size=(width, height),
|
||||
transition='in_scale',
|
||||
),
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, (height - 60) if message2 is None else (height - 50)),
|
||||
size=(width, 25),
|
||||
text=message,
|
||||
maxwidth=width * 0.9,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
if message2 is not None:
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, height - 60),
|
||||
size=(0, 0),
|
||||
text=message2,
|
||||
maxwidth=width * 0.9,
|
||||
scale=0.47,
|
||||
color=(0.7, 1.0, 0.7, 0.6),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
self._counter = 5
|
||||
self._count_down_text = ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
h_align='center',
|
||||
position=(0, height - 110),
|
||||
size=(width, 25),
|
||||
color=(1, 1, 1, 0.3),
|
||||
text=str(self._counter),
|
||||
)
|
||||
self._decrement_timer: ba.Timer | None = ba.Timer(
|
||||
1.0,
|
||||
ba.Call(self._decrement),
|
||||
repeat=True,
|
||||
timetype=ba.TimeType.REAL,
|
||||
)
|
||||
ba.internal.capture_gamepad_input(ba.WeakCall(self._event_callback))
|
||||
|
||||
def __del__(self) -> None:
|
||||
pass
|
||||
|
||||
def die(self) -> None:
|
||||
"""Kill the window."""
|
||||
|
||||
# This strong-refs us; killing it allow us to die now.
|
||||
self._decrement_timer = None
|
||||
ba.internal.release_gamepad_input()
|
||||
if self._root_widget:
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||
|
||||
def _event_callback(self, event: dict[str, Any]) -> None:
|
||||
input_device = event['input_device']
|
||||
assert isinstance(input_device, ba.InputDevice)
|
||||
|
||||
# Update - we now allow *any* input device of this type.
|
||||
if (
|
||||
self._input
|
||||
and input_device
|
||||
and input_device.name == self._input.name
|
||||
):
|
||||
self._callback(self._capture_button, event, self)
|
||||
|
||||
def _decrement(self) -> None:
|
||||
self._counter -= 1
|
||||
if self._counter >= 1:
|
||||
if self._count_down_text:
|
||||
ba.textwidget(
|
||||
edit=self._count_down_text, text=str(self._counter)
|
||||
)
|
||||
else:
|
||||
ba.playsound(ba.getsound('error'))
|
||||
self.die()
|
||||
575
dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py
vendored
Normal file
575
dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py
vendored
Normal file
|
|
@ -0,0 +1,575 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality related to advanced gamepad configuring."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
from bastd.ui.settings import gamepad as gpsui
|
||||
|
||||
|
||||
class GamepadAdvancedSettingsWindow(ba.Window):
|
||||
"""Window for advanced gamepad configuration."""
|
||||
|
||||
def __init__(self, parent_window: gpsui.GamepadSettingsWindow):
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-locals
|
||||
self._parent_window = parent_window
|
||||
|
||||
app = ba.app
|
||||
|
||||
self._r = parent_window.get_r()
|
||||
uiscale = ba.app.ui.uiscale
|
||||
self._width = 900 if uiscale is ba.UIScale.SMALL else 700
|
||||
self._x_inset = x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
|
||||
self._height = 402 if uiscale is ba.UIScale.SMALL else 512
|
||||
self._textwidgets: dict[str, ba.Widget] = {}
|
||||
advb = parent_window.get_advanced_button()
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
transition='in_scale',
|
||||
size=(self._width, self._height),
|
||||
scale=1.06
|
||||
* (
|
||||
1.85
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.35
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(0, -25)
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else (0, 0),
|
||||
scale_origin_stack_offset=(advb.get_screen_space_center()),
|
||||
)
|
||||
)
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._width * 0.5,
|
||||
self._height - (40 if uiscale is ba.UIScale.SMALL else 34),
|
||||
),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.advancedTitleText'),
|
||||
maxwidth=320,
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
back_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
position=(
|
||||
self._width - (176 + x_inset),
|
||||
self._height - (60 if uiscale is ba.UIScale.SMALL else 55),
|
||||
),
|
||||
size=(120, 48),
|
||||
text_scale=0.8,
|
||||
label=ba.Lstr(resource='doneText'),
|
||||
on_activate_call=self._done,
|
||||
)
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget,
|
||||
start_button=btn,
|
||||
on_cancel_call=btn.activate,
|
||||
)
|
||||
|
||||
self._scroll_width = self._width - (100 + 2 * x_inset)
|
||||
self._scroll_height = self._height - 110
|
||||
self._sub_width = self._scroll_width - 20
|
||||
self._sub_height = (
|
||||
940 if self._parent_window.get_is_secondary() else 1040
|
||||
)
|
||||
if app.vr_mode:
|
||||
self._sub_height += 50
|
||||
self._scrollwidget = ba.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
(self._width - self._scroll_width) * 0.5,
|
||||
self._height - 65 - self._scroll_height,
|
||||
),
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
claims_left_right=True,
|
||||
claims_tab=True,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
self._subcontainer = ba.containerwidget(
|
||||
parent=self._scrollwidget,
|
||||
size=(self._sub_width, self._sub_height),
|
||||
background=False,
|
||||
claims_left_right=True,
|
||||
claims_tab=True,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._scrollwidget
|
||||
)
|
||||
|
||||
h = 30
|
||||
v = self._sub_height - 10
|
||||
|
||||
h2 = h + 12
|
||||
|
||||
# don't allow secondary joysticks to handle unassigned buttons
|
||||
if not self._parent_window.get_is_secondary():
|
||||
v -= 40
|
||||
cb1 = ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h + 70, v),
|
||||
size=(500, 30),
|
||||
text=ba.Lstr(resource=self._r + '.unassignedButtonsRunText'),
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
maxwidth=330,
|
||||
scale=1.0,
|
||||
on_value_change_call=(
|
||||
self._parent_window.set_unassigned_buttons_run_value
|
||||
),
|
||||
autoselect=True,
|
||||
value=self._parent_window.get_unassigned_buttons_run_value(),
|
||||
)
|
||||
ba.widget(edit=cb1, up_widget=back_button)
|
||||
v -= 60
|
||||
capb = self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=ba.Lstr(resource=self._r + '.runButton1Text'),
|
||||
control='buttonRun1' + self._parent_window.get_ext(),
|
||||
)
|
||||
if self._parent_window.get_is_secondary():
|
||||
for widget in capb:
|
||||
ba.widget(edit=widget, up_widget=back_button)
|
||||
v -= 42
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=ba.Lstr(resource=self._r + '.runButton2Text'),
|
||||
control='buttonRun2' + self._parent_window.get_ext(),
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 24),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.runTriggerDescriptionText'),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
scale=0.7,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
v -= 85
|
||||
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=ba.Lstr(resource=self._r + '.runTrigger1Text'),
|
||||
control='triggerRun1' + self._parent_window.get_ext(),
|
||||
message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText'),
|
||||
)
|
||||
v -= 42
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=ba.Lstr(resource=self._r + '.runTrigger2Text'),
|
||||
control='triggerRun2' + self._parent_window.get_ext(),
|
||||
message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText'),
|
||||
)
|
||||
|
||||
# in vr mode, allow assigning a reset-view button
|
||||
if app.vr_mode:
|
||||
v -= 50
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=ba.Lstr(resource=self._r + '.vrReorientButtonText'),
|
||||
control='buttonVRReorient' + self._parent_window.get_ext(),
|
||||
)
|
||||
|
||||
v -= 60
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=ba.Lstr(resource=self._r + '.extraStartButtonText'),
|
||||
control='buttonStart2' + self._parent_window.get_ext(),
|
||||
)
|
||||
v -= 60
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=ba.Lstr(resource=self._r + '.ignoredButton1Text'),
|
||||
control='buttonIgnored' + self._parent_window.get_ext(),
|
||||
)
|
||||
v -= 42
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=ba.Lstr(resource=self._r + '.ignoredButton2Text'),
|
||||
control='buttonIgnored2' + self._parent_window.get_ext(),
|
||||
)
|
||||
v -= 42
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=ba.Lstr(resource=self._r + '.ignoredButton3Text'),
|
||||
control='buttonIgnored3' + self._parent_window.get_ext(),
|
||||
)
|
||||
v -= 42
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
name=ba.Lstr(resource=self._r + '.ignoredButton4Text'),
|
||||
control='buttonIgnored4' + self._parent_window.get_ext(),
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 14),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.ignoredButtonDescriptionText'),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
scale=0.8,
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
v -= 80
|
||||
pwin = self._parent_window
|
||||
ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
autoselect=True,
|
||||
position=(h + 50, v),
|
||||
size=(400, 30),
|
||||
text=ba.Lstr(resource=self._r + '.startButtonActivatesDefaultText'),
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
maxwidth=450,
|
||||
scale=0.9,
|
||||
on_value_change_call=(
|
||||
pwin.set_start_button_activates_default_widget_value
|
||||
),
|
||||
value=pwin.get_start_button_activates_default_widget_value(),
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 12),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(
|
||||
resource=self._r + '.startButtonActivatesDefaultDescriptionText'
|
||||
),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
scale=0.7,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
v -= 80
|
||||
ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
autoselect=True,
|
||||
position=(h + 50, v),
|
||||
size=(400, 30),
|
||||
text=ba.Lstr(resource=self._r + '.uiOnlyText'),
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
maxwidth=450,
|
||||
scale=0.9,
|
||||
on_value_change_call=self._parent_window.set_ui_only_value,
|
||||
value=self._parent_window.get_ui_only_value(),
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 12),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.uiOnlyDescriptionText'),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
scale=0.7,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
v -= 80
|
||||
ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
autoselect=True,
|
||||
position=(h + 50, v),
|
||||
size=(400, 30),
|
||||
text=ba.Lstr(resource=self._r + '.ignoreCompletelyText'),
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
maxwidth=450,
|
||||
scale=0.9,
|
||||
on_value_change_call=pwin.set_ignore_completely_value,
|
||||
value=self._parent_window.get_ignore_completely_value(),
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 12),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.ignoreCompletelyDescriptionText'),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
scale=0.7,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
v -= 80
|
||||
|
||||
cb1 = ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
autoselect=True,
|
||||
position=(h + 50, v),
|
||||
size=(400, 30),
|
||||
text=ba.Lstr(resource=self._r + '.autoRecalibrateText'),
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
maxwidth=450,
|
||||
scale=0.9,
|
||||
on_value_change_call=pwin.set_auto_recalibrate_analog_stick_value,
|
||||
value=self._parent_window.get_auto_recalibrate_analog_stick_value(),
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 12),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.autoRecalibrateDescriptionText'),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
scale=0.7,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= 80
|
||||
|
||||
buttons = self._config_value_editor(
|
||||
ba.Lstr(resource=self._r + '.analogStickDeadZoneText'),
|
||||
control=('analogStickDeadZone' + self._parent_window.get_ext()),
|
||||
position=(h + 40, v),
|
||||
min_val=0,
|
||||
max_val=10.0,
|
||||
increment=0.1,
|
||||
x_offset=100,
|
||||
)
|
||||
ba.widget(edit=buttons[0], left_widget=cb1, up_widget=cb1)
|
||||
ba.widget(edit=cb1, right_widget=buttons[0], down_widget=buttons[0])
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v - 12),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(
|
||||
resource=self._r + '.analogStickDeadZoneDescriptionText'
|
||||
),
|
||||
color=(0.7, 1, 0.7, 0.6),
|
||||
maxwidth=self._sub_width * 0.8,
|
||||
scale=0.7,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= 100
|
||||
|
||||
# child joysticks cant have child joysticks.. that's just
|
||||
# crazy talk
|
||||
if not self._parent_window.get_is_secondary():
|
||||
ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource=self._r + '.twoInOneSetupText'),
|
||||
position=(40, v),
|
||||
size=(self._sub_width - 80, 50),
|
||||
on_activate_call=self._parent_window.show_secondary_editor,
|
||||
up_widget=buttons[0],
|
||||
)
|
||||
|
||||
# set a bigger bottom show-buffer for the widgets we just made
|
||||
# so we can see the text below them when navigating with
|
||||
# a gamepad
|
||||
for child in self._subcontainer.get_children():
|
||||
ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=30)
|
||||
|
||||
def _capture_button(
|
||||
self,
|
||||
pos: tuple[float, float],
|
||||
name: ba.Lstr,
|
||||
control: str,
|
||||
message: ba.Lstr | None = None,
|
||||
) -> tuple[ba.Widget, ba.Widget]:
|
||||
if message is None:
|
||||
message = ba.Lstr(
|
||||
resource=self._parent_window.get_r() + '.pressAnyButtonText'
|
||||
)
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
autoselect=True,
|
||||
position=(pos[0], pos[1]),
|
||||
label=name,
|
||||
size=(250, 60),
|
||||
scale=0.7,
|
||||
)
|
||||
btn2 = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
autoselect=True,
|
||||
position=(pos[0] + 400, pos[1] + 2),
|
||||
left_widget=btn,
|
||||
color=(0.45, 0.4, 0.5),
|
||||
textcolor=(0.65, 0.6, 0.7),
|
||||
label=ba.Lstr(resource=self._r + '.clearText'),
|
||||
size=(110, 50),
|
||||
scale=0.7,
|
||||
on_activate_call=ba.Call(self._clear_control, control),
|
||||
)
|
||||
ba.widget(edit=btn, right_widget=btn2)
|
||||
|
||||
# make this in a timer so that it shows up on top of all
|
||||
# other buttons
|
||||
|
||||
def doit() -> None:
|
||||
from bastd.ui.settings import gamepad
|
||||
|
||||
txt = ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(pos[0] + 285, pos[1] + 20),
|
||||
color=(1, 1, 1, 0.3),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=0.7,
|
||||
text=self._parent_window.get_control_value_name(control),
|
||||
maxwidth=200,
|
||||
)
|
||||
self._textwidgets[control] = txt
|
||||
ba.buttonwidget(
|
||||
edit=btn,
|
||||
on_activate_call=ba.Call(
|
||||
gamepad.AwaitGamepadInputWindow,
|
||||
self._parent_window.get_input(),
|
||||
control,
|
||||
self._gamepad_event,
|
||||
message,
|
||||
),
|
||||
)
|
||||
|
||||
ba.timer(0, doit, timetype=ba.TimeType.REAL)
|
||||
return btn, btn2
|
||||
|
||||
def _inc(
|
||||
self, control: str, min_val: float, max_val: float, inc: float
|
||||
) -> None:
|
||||
val = self._parent_window.get_settings().get(control, 1.0)
|
||||
val = min(max_val, max(min_val, val + inc))
|
||||
if abs(1.0 - val) < 0.001:
|
||||
if control in self._parent_window.get_settings():
|
||||
del self._parent_window.get_settings()[control]
|
||||
else:
|
||||
self._parent_window.get_settings()[control] = round(val, 1)
|
||||
ba.textwidget(
|
||||
edit=self._textwidgets[control],
|
||||
text=self._parent_window.get_control_value_name(control),
|
||||
)
|
||||
|
||||
def _config_value_editor(
|
||||
self,
|
||||
name: ba.Lstr,
|
||||
control: str,
|
||||
position: tuple[float, float],
|
||||
min_val: float = 0.0,
|
||||
max_val: float = 100.0,
|
||||
increment: float = 1.0,
|
||||
change_sound: bool = True,
|
||||
x_offset: float = 0.0,
|
||||
displayname: ba.Lstr | None = None,
|
||||
) -> tuple[ba.Widget, ba.Widget]:
|
||||
|
||||
if displayname is None:
|
||||
displayname = name
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=position,
|
||||
size=(100, 30),
|
||||
text=displayname,
|
||||
color=(0.8, 0.8, 0.8, 1.0),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
scale=1.0,
|
||||
maxwidth=280,
|
||||
)
|
||||
self._textwidgets[control] = ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(246.0 + x_offset, position[1]),
|
||||
size=(60, 28),
|
||||
editable=False,
|
||||
color=(0.3, 1.0, 0.3, 1.0),
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
text=self._parent_window.get_control_value_name(control),
|
||||
padding=2,
|
||||
)
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
autoselect=True,
|
||||
position=(330 + x_offset, position[1] + 4),
|
||||
size=(28, 28),
|
||||
label='-',
|
||||
on_activate_call=ba.Call(
|
||||
self._inc, control, min_val, max_val, -increment
|
||||
),
|
||||
repeat=True,
|
||||
enable_sound=(change_sound is True),
|
||||
)
|
||||
btn2 = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
autoselect=True,
|
||||
position=(380 + x_offset, position[1] + 4),
|
||||
size=(28, 28),
|
||||
label='+',
|
||||
on_activate_call=ba.Call(
|
||||
self._inc, control, min_val, max_val, increment
|
||||
),
|
||||
repeat=True,
|
||||
enable_sound=(change_sound is True),
|
||||
)
|
||||
return btn, btn2
|
||||
|
||||
def _clear_control(self, control: str) -> None:
|
||||
if control in self._parent_window.get_settings():
|
||||
del self._parent_window.get_settings()[control]
|
||||
ba.textwidget(
|
||||
edit=self._textwidgets[control],
|
||||
text=self._parent_window.get_control_value_name(control),
|
||||
)
|
||||
|
||||
def _gamepad_event(
|
||||
self,
|
||||
control: str,
|
||||
event: dict[str, Any],
|
||||
dialog: gpsui.AwaitGamepadInputWindow,
|
||||
) -> None:
|
||||
ext = self._parent_window.get_ext()
|
||||
if control in ['triggerRun1' + ext, 'triggerRun2' + ext]:
|
||||
if event['type'] == 'AXISMOTION':
|
||||
# ignore small values or else we might get triggered
|
||||
# by noise
|
||||
if abs(event['value']) > 0.5:
|
||||
self._parent_window.get_settings()[control] = event['axis']
|
||||
# update the button's text widget
|
||||
if self._textwidgets[control]:
|
||||
ba.textwidget(
|
||||
edit=self._textwidgets[control],
|
||||
text=self._parent_window.get_control_value_name(
|
||||
control
|
||||
),
|
||||
)
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
dialog.die()
|
||||
else:
|
||||
if event['type'] == 'BUTTONDOWN':
|
||||
value = event['button']
|
||||
self._parent_window.get_settings()[control] = value
|
||||
# update the button's text widget
|
||||
if self._textwidgets[control]:
|
||||
ba.textwidget(
|
||||
edit=self._textwidgets[control],
|
||||
text=self._parent_window.get_control_value_name(
|
||||
control
|
||||
),
|
||||
)
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
dialog.die()
|
||||
|
||||
def _done(self) -> None:
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||
192
dist/ba_data/python/bastd/ui/settings/gamepadselect.py
vendored
Normal file
192
dist/ba_data/python/bastd/ui/settings/gamepadselect.py
vendored
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Settings UI related to gamepad functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
def gamepad_configure_callback(event: dict[str, Any]) -> None:
|
||||
"""Respond to a gamepad button press during config selection."""
|
||||
from ba.internal import get_remote_app_name
|
||||
from bastd.ui.settings import gamepad
|
||||
|
||||
# Ignore all but button-presses.
|
||||
if event['type'] not in ['BUTTONDOWN', 'HATMOTION']:
|
||||
return
|
||||
ba.internal.release_gamepad_input()
|
||||
try:
|
||||
ba.app.ui.clear_main_menu_window(transition='out_left')
|
||||
except Exception:
|
||||
ba.print_exception('Error transitioning out main_menu_window.')
|
||||
ba.playsound(ba.getsound('activateBeep'))
|
||||
ba.playsound(ba.getsound('swish'))
|
||||
inputdevice = event['input_device']
|
||||
assert isinstance(inputdevice, ba.InputDevice)
|
||||
if inputdevice.allows_configuring:
|
||||
ba.app.ui.set_main_menu_window(
|
||||
gamepad.GamepadSettingsWindow(inputdevice).get_root_widget()
|
||||
)
|
||||
else:
|
||||
width = 700
|
||||
height = 200
|
||||
button_width = 100
|
||||
uiscale = ba.app.ui.uiscale
|
||||
dlg = ba.containerwidget(
|
||||
scale=(
|
||||
1.7
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.4
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
size=(width, height),
|
||||
transition='in_right',
|
||||
)
|
||||
ba.app.ui.set_main_menu_window(dlg)
|
||||
device_name = inputdevice.name
|
||||
if device_name == 'iDevice':
|
||||
msg = ba.Lstr(
|
||||
resource='bsRemoteConfigureInAppText',
|
||||
subs=[('${REMOTE_APP_NAME}', get_remote_app_name())],
|
||||
)
|
||||
else:
|
||||
msg = ba.Lstr(
|
||||
resource='cantConfigureDeviceText',
|
||||
subs=[('${DEVICE}', device_name)],
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=dlg,
|
||||
position=(0, height - 80),
|
||||
size=(width, 25),
|
||||
text=msg,
|
||||
scale=0.8,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
)
|
||||
|
||||
def _ok() -> None:
|
||||
from bastd.ui.settings import controls
|
||||
|
||||
ba.containerwidget(edit=dlg, transition='out_right')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
controls.ControlsSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
ba.buttonwidget(
|
||||
parent=dlg,
|
||||
position=((width - button_width) / 2, 20),
|
||||
size=(button_width, 60),
|
||||
label=ba.Lstr(resource='okText'),
|
||||
on_activate_call=_ok,
|
||||
)
|
||||
|
||||
|
||||
class GamepadSelectWindow(ba.Window):
|
||||
"""Window for selecting a gamepad to configure."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
from typing import cast
|
||||
|
||||
width = 480
|
||||
height = 170
|
||||
spacing = 40
|
||||
self._r = 'configGamepadSelectWindow'
|
||||
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
scale=(
|
||||
2.3
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.5
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
size=(width, height),
|
||||
transition='in_right',
|
||||
)
|
||||
)
|
||||
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(20, height - 60),
|
||||
size=(130, 60),
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
scale=0.8,
|
||||
on_activate_call=self._back,
|
||||
)
|
||||
# Let's not have anything selected by default; its misleading looking
|
||||
# for the controller getting configured.
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget,
|
||||
cancel_button=btn,
|
||||
selected_child=cast(ba.Widget, 0),
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(20, height - 50),
|
||||
size=(width, 25),
|
||||
text=ba.Lstr(resource=self._r + '.titleText'),
|
||||
maxwidth=250,
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
ba.buttonwidget(
|
||||
edit=btn,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
v: float = height - 60
|
||||
v -= spacing
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(15, v),
|
||||
size=(width - 30, 30),
|
||||
scale=0.8,
|
||||
text=ba.Lstr(resource=self._r + '.pressAnyButtonText'),
|
||||
maxwidth=width * 0.95,
|
||||
color=ba.app.ui.infotextcolor,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
)
|
||||
v -= spacing * 1.24
|
||||
if ba.app.platform == 'android':
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(15, v),
|
||||
size=(width - 30, 30),
|
||||
scale=0.46,
|
||||
text=ba.Lstr(resource=self._r + '.androidNoteText'),
|
||||
maxwidth=width * 0.95,
|
||||
color=(0.7, 0.9, 0.7, 0.5),
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
)
|
||||
|
||||
ba.internal.capture_gamepad_input(gamepad_configure_callback)
|
||||
|
||||
def _back(self) -> None:
|
||||
from bastd.ui.settings import controls
|
||||
|
||||
ba.internal.release_gamepad_input()
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
controls.ControlsSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
)
|
||||
484
dist/ba_data/python/bastd/ui/settings/graphics.py
vendored
Normal file
484
dist/ba_data/python/bastd/ui/settings/graphics.py
vendored
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides UI for graphics settings."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class GraphicsSettingsWindow(ba.Window):
|
||||
"""Window for graphics settings."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str = 'in_right',
|
||||
origin_widget: ba.Widget | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
from bastd.ui import popup
|
||||
from bastd.ui.config import ConfigCheckBox, ConfigNumberEdit
|
||||
|
||||
# if they provided an origin-widget, scale up from that
|
||||
scale_origin: tuple[float, float] | None
|
||||
if origin_widget is not None:
|
||||
self._transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
|
||||
self._r = 'graphicsSettingsWindow'
|
||||
app = ba.app
|
||||
|
||||
spacing = 32
|
||||
self._have_selected_child = False
|
||||
uiscale = app.ui.uiscale
|
||||
width = 450.0
|
||||
height = 302.0
|
||||
|
||||
self._show_fullscreen = False
|
||||
fullscreen_spacing_top = spacing * 0.2
|
||||
fullscreen_spacing = spacing * 1.2
|
||||
if uiscale == ba.UIScale.LARGE and app.platform != 'android':
|
||||
self._show_fullscreen = True
|
||||
height += fullscreen_spacing + fullscreen_spacing_top
|
||||
|
||||
show_gamma = False
|
||||
gamma_spacing = spacing * 1.3
|
||||
if ba.internal.has_gamma_control():
|
||||
show_gamma = True
|
||||
height += gamma_spacing
|
||||
|
||||
show_vsync = False
|
||||
if app.platform == 'mac':
|
||||
show_vsync = True
|
||||
|
||||
show_resolution = True
|
||||
if app.vr_mode:
|
||||
show_resolution = (
|
||||
app.platform == 'android' and app.subplatform == 'cardboard'
|
||||
)
|
||||
|
||||
uiscale = ba.app.ui.uiscale
|
||||
base_scale = (
|
||||
2.4
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.5
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
)
|
||||
popup_menu_scale = base_scale * 1.2
|
||||
v = height - 50
|
||||
v -= spacing * 1.15
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(width, height),
|
||||
transition=transition,
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
scale=base_scale,
|
||||
stack_offset=(0, -30)
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else (0, 0),
|
||||
)
|
||||
)
|
||||
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(35, height - 50),
|
||||
size=(120, 60),
|
||||
scale=0.8,
|
||||
text_scale=1.2,
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
on_activate_call=self._back,
|
||||
)
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, height - 44),
|
||||
size=(width, 25),
|
||||
text=ba.Lstr(resource=self._r + '.titleText'),
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
)
|
||||
|
||||
ba.buttonwidget(
|
||||
edit=btn,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
self._fullscreen_checkbox: ba.Widget | None
|
||||
if self._show_fullscreen:
|
||||
v -= fullscreen_spacing_top
|
||||
self._fullscreen_checkbox = ConfigCheckBox(
|
||||
parent=self._root_widget,
|
||||
position=(100, v),
|
||||
maxwidth=200,
|
||||
size=(300, 30),
|
||||
configkey='Fullscreen',
|
||||
displayname=ba.Lstr(
|
||||
resource=self._r
|
||||
+ (
|
||||
'.fullScreenCmdText'
|
||||
if app.platform == 'mac'
|
||||
else '.fullScreenCtrlText'
|
||||
)
|
||||
),
|
||||
).widget
|
||||
if not self._have_selected_child:
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget,
|
||||
selected_child=self._fullscreen_checkbox,
|
||||
)
|
||||
self._have_selected_child = True
|
||||
v -= fullscreen_spacing
|
||||
else:
|
||||
self._fullscreen_checkbox = None
|
||||
|
||||
self._gamma_controls: ConfigNumberEdit | None
|
||||
if show_gamma:
|
||||
self._gamma_controls = gmc = ConfigNumberEdit(
|
||||
parent=self._root_widget,
|
||||
position=(90, v),
|
||||
configkey='Screen Gamma',
|
||||
displayname=ba.Lstr(resource=self._r + '.gammaText'),
|
||||
minval=0.1,
|
||||
maxval=2.0,
|
||||
increment=0.1,
|
||||
xoffset=-70,
|
||||
textscale=0.85,
|
||||
)
|
||||
if ba.app.ui.use_toolbars:
|
||||
ba.widget(
|
||||
edit=gmc.plusbutton,
|
||||
right_widget=ba.internal.get_special_widget('party_button'),
|
||||
)
|
||||
if not self._have_selected_child:
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, selected_child=gmc.minusbutton
|
||||
)
|
||||
self._have_selected_child = True
|
||||
v -= gamma_spacing
|
||||
else:
|
||||
self._gamma_controls = None
|
||||
|
||||
self._selected_color = (0.5, 1, 0.5, 1)
|
||||
self._unselected_color = (0.7, 0.7, 0.7, 1)
|
||||
|
||||
# quality
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(60, v),
|
||||
size=(160, 25),
|
||||
text=ba.Lstr(resource=self._r + '.visualsText'),
|
||||
color=ba.app.ui.heading_color,
|
||||
scale=0.65,
|
||||
maxwidth=150,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
popup.PopupMenu(
|
||||
parent=self._root_widget,
|
||||
position=(60, v - 50),
|
||||
width=150,
|
||||
scale=popup_menu_scale,
|
||||
choices=['Auto', 'Higher', 'High', 'Medium', 'Low'],
|
||||
choices_disabled=['Higher', 'High']
|
||||
if ba.internal.get_max_graphics_quality() == 'Medium'
|
||||
else [],
|
||||
choices_display=[
|
||||
ba.Lstr(resource='autoText'),
|
||||
ba.Lstr(resource=self._r + '.higherText'),
|
||||
ba.Lstr(resource=self._r + '.highText'),
|
||||
ba.Lstr(resource=self._r + '.mediumText'),
|
||||
ba.Lstr(resource=self._r + '.lowText'),
|
||||
],
|
||||
current_choice=ba.app.config.resolve('Graphics Quality'),
|
||||
on_value_change_call=self._set_quality,
|
||||
)
|
||||
|
||||
# texture controls
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(230, v),
|
||||
size=(160, 25),
|
||||
text=ba.Lstr(resource=self._r + '.texturesText'),
|
||||
color=ba.app.ui.heading_color,
|
||||
scale=0.65,
|
||||
maxwidth=150,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
textures_popup = popup.PopupMenu(
|
||||
parent=self._root_widget,
|
||||
position=(230, v - 50),
|
||||
width=150,
|
||||
scale=popup_menu_scale,
|
||||
choices=['Auto', 'High', 'Medium', 'Low'],
|
||||
choices_display=[
|
||||
ba.Lstr(resource='autoText'),
|
||||
ba.Lstr(resource=self._r + '.highText'),
|
||||
ba.Lstr(resource=self._r + '.mediumText'),
|
||||
ba.Lstr(resource=self._r + '.lowText'),
|
||||
],
|
||||
current_choice=ba.app.config.resolve('Texture Quality'),
|
||||
on_value_change_call=self._set_textures,
|
||||
)
|
||||
if ba.app.ui.use_toolbars:
|
||||
ba.widget(
|
||||
edit=textures_popup.get_button(),
|
||||
right_widget=ba.internal.get_special_widget('party_button'),
|
||||
)
|
||||
v -= 80
|
||||
|
||||
h_offs = 0
|
||||
|
||||
if show_resolution:
|
||||
# resolution
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(h_offs + 60, v),
|
||||
size=(160, 25),
|
||||
text=ba.Lstr(resource=self._r + '.resolutionText'),
|
||||
color=ba.app.ui.heading_color,
|
||||
scale=0.65,
|
||||
maxwidth=150,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
# on standard android we have 'Auto', 'Native', and a few
|
||||
# HD standards
|
||||
if app.platform == 'android':
|
||||
# on cardboard/daydream android we have a few
|
||||
# render-target-scale options
|
||||
if app.subplatform == 'cardboard':
|
||||
current_res_cardboard = (
|
||||
str(
|
||||
min(
|
||||
100,
|
||||
max(
|
||||
10,
|
||||
int(
|
||||
round(
|
||||
ba.app.config.resolve(
|
||||
'GVR Render Target Scale'
|
||||
)
|
||||
* 100.0
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
+ '%'
|
||||
) # yapf: disable
|
||||
popup.PopupMenu(
|
||||
parent=self._root_widget,
|
||||
position=(h_offs + 60, v - 50),
|
||||
width=120,
|
||||
scale=popup_menu_scale,
|
||||
choices=['100%', '75%', '50%', '35%'],
|
||||
current_choice=current_res_cardboard,
|
||||
on_value_change_call=self._set_gvr_render_target_scale,
|
||||
)
|
||||
else:
|
||||
native_res = ba.internal.get_display_resolution()
|
||||
assert native_res is not None
|
||||
choices = ['Auto', 'Native']
|
||||
choices_display = [
|
||||
ba.Lstr(resource='autoText'),
|
||||
ba.Lstr(resource='nativeText'),
|
||||
]
|
||||
for res in [1440, 1080, 960, 720, 480]:
|
||||
# nav bar is 72px so lets allow for that in what
|
||||
# choices we show
|
||||
if native_res[1] >= res - 72:
|
||||
res_str = str(res) + 'p'
|
||||
choices.append(res_str)
|
||||
choices_display.append(ba.Lstr(value=res_str))
|
||||
current_res_android = ba.app.config.resolve(
|
||||
'Resolution (Android)'
|
||||
)
|
||||
popup.PopupMenu(
|
||||
parent=self._root_widget,
|
||||
position=(h_offs + 60, v - 50),
|
||||
width=120,
|
||||
scale=popup_menu_scale,
|
||||
choices=choices,
|
||||
choices_display=choices_display,
|
||||
current_choice=current_res_android,
|
||||
on_value_change_call=self._set_android_res,
|
||||
)
|
||||
else:
|
||||
# if we're on a system that doesn't allow setting resolution,
|
||||
# set pixel-scale instead
|
||||
current_res = ba.internal.get_display_resolution()
|
||||
if current_res is None:
|
||||
current_res2 = (
|
||||
str(
|
||||
min(
|
||||
100,
|
||||
max(
|
||||
10,
|
||||
int(
|
||||
round(
|
||||
ba.app.config.resolve(
|
||||
'Screen Pixel Scale'
|
||||
)
|
||||
* 100.0
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
+ '%'
|
||||
) # yapf: disable
|
||||
popup.PopupMenu(
|
||||
parent=self._root_widget,
|
||||
position=(h_offs + 60, v - 50),
|
||||
width=120,
|
||||
scale=popup_menu_scale,
|
||||
choices=['100%', '88%', '75%', '63%', '50%'],
|
||||
current_choice=current_res2,
|
||||
on_value_change_call=self._set_pixel_scale,
|
||||
)
|
||||
else:
|
||||
raise Exception(
|
||||
'obsolete path; discrete resolutions'
|
||||
' no longer supported'
|
||||
)
|
||||
|
||||
# vsync
|
||||
if show_vsync:
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(230, v),
|
||||
size=(160, 25),
|
||||
text=ba.Lstr(resource=self._r + '.verticalSyncText'),
|
||||
color=ba.app.ui.heading_color,
|
||||
scale=0.65,
|
||||
maxwidth=150,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
popup.PopupMenu(
|
||||
parent=self._root_widget,
|
||||
position=(230, v - 50),
|
||||
width=150,
|
||||
scale=popup_menu_scale,
|
||||
choices=['Auto', 'Always', 'Never'],
|
||||
choices_display=[
|
||||
ba.Lstr(resource='autoText'),
|
||||
ba.Lstr(resource=self._r + '.alwaysText'),
|
||||
ba.Lstr(resource=self._r + '.neverText'),
|
||||
],
|
||||
current_choice=ba.app.config.resolve('Vertical Sync'),
|
||||
on_value_change_call=self._set_vsync,
|
||||
)
|
||||
|
||||
v -= 90
|
||||
fpsc = ConfigCheckBox(
|
||||
parent=self._root_widget,
|
||||
position=(69, v - 6),
|
||||
size=(210, 30),
|
||||
scale=0.86,
|
||||
configkey='Show FPS',
|
||||
displayname=ba.Lstr(resource=self._r + '.showFPSText'),
|
||||
maxwidth=130,
|
||||
)
|
||||
|
||||
# (tv mode doesnt apply to vr)
|
||||
if not ba.app.vr_mode:
|
||||
tvc = ConfigCheckBox(
|
||||
parent=self._root_widget,
|
||||
position=(240, v - 6),
|
||||
size=(210, 30),
|
||||
scale=0.86,
|
||||
configkey='TV Border',
|
||||
displayname=ba.Lstr(resource=self._r + '.tvBorderText'),
|
||||
maxwidth=130,
|
||||
)
|
||||
# grumble..
|
||||
ba.widget(edit=fpsc.widget, right_widget=tvc.widget)
|
||||
try:
|
||||
pass
|
||||
|
||||
except Exception:
|
||||
ba.print_exception('Exception wiring up graphics settings UI:')
|
||||
|
||||
v -= spacing
|
||||
|
||||
# make a timer to update our controls in case the config changes
|
||||
# under us
|
||||
self._update_timer = ba.Timer(
|
||||
0.25,
|
||||
ba.WeakCall(self._update_controls),
|
||||
repeat=True,
|
||||
timetype=ba.TimeType.REAL,
|
||||
)
|
||||
|
||||
def _back(self) -> None:
|
||||
from bastd.ui.settings import allsettings
|
||||
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
ba.app.ui.set_main_menu_window(
|
||||
allsettings.AllSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
def _set_quality(self, quality: str) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg['Graphics Quality'] = quality
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _set_textures(self, val: str) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg['Texture Quality'] = val
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _set_android_res(self, val: str) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg['Resolution (Android)'] = val
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _set_pixel_scale(self, res: str) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg['Screen Pixel Scale'] = float(res[:-1]) / 100.0
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _set_gvr_render_target_scale(self, res: str) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg['GVR Render Target Scale'] = float(res[:-1]) / 100.0
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _set_vsync(self, val: str) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg['Vertical Sync'] = val
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _update_controls(self) -> None:
|
||||
if self._show_fullscreen:
|
||||
ba.checkboxwidget(
|
||||
edit=self._fullscreen_checkbox,
|
||||
value=ba.app.config.resolve('Fullscreen'),
|
||||
)
|
||||
381
dist/ba_data/python/bastd/ui/settings/keyboard.py
vendored
Normal file
381
dist/ba_data/python/bastd/ui/settings/keyboard.py
vendored
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Keyboard settings related UI functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class ConfigKeyboardWindow(ba.Window):
|
||||
"""Window for configuring keyboards."""
|
||||
|
||||
def __init__(self, c: ba.InputDevice, transition: str = 'in_right'):
|
||||
self._r = 'configKeyboardWindow'
|
||||
self._input = c
|
||||
self._name = self._input.name
|
||||
self._unique_id = self._input.unique_identifier
|
||||
dname_raw = self._name
|
||||
if self._unique_id != '#1':
|
||||
dname_raw += ' ' + self._unique_id.replace('#', 'P')
|
||||
self._displayname = ba.Lstr(translate=('inputDeviceNames', dname_raw))
|
||||
self._width = 700
|
||||
if self._unique_id != '#1':
|
||||
self._height = 480
|
||||
else:
|
||||
self._height = 375
|
||||
self._spacing = 40
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
scale=(
|
||||
1.6
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.3
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(0, 5) if uiscale is ba.UIScale.SMALL else (0, 0),
|
||||
transition=transition,
|
||||
)
|
||||
)
|
||||
|
||||
self._rebuild_ui()
|
||||
|
||||
def _rebuild_ui(self) -> None:
|
||||
from ba.internal import get_device_value
|
||||
|
||||
for widget in self._root_widget.get_children():
|
||||
widget.delete()
|
||||
|
||||
# Fill our temp config with present values.
|
||||
self._settings: dict[str, int] = {}
|
||||
for button in [
|
||||
'buttonJump',
|
||||
'buttonPunch',
|
||||
'buttonBomb',
|
||||
'buttonPickUp',
|
||||
'buttonStart',
|
||||
'buttonStart2',
|
||||
'buttonUp',
|
||||
'buttonDown',
|
||||
'buttonLeft',
|
||||
'buttonRight',
|
||||
]:
|
||||
self._settings[button] = get_device_value(self._input, button)
|
||||
|
||||
cancel_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
position=(38, self._height - 85),
|
||||
size=(170, 60),
|
||||
label=ba.Lstr(resource='cancelText'),
|
||||
scale=0.9,
|
||||
on_activate_call=self._cancel,
|
||||
)
|
||||
save_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
position=(self._width - 190, self._height - 85),
|
||||
size=(180, 60),
|
||||
label=ba.Lstr(resource='saveText'),
|
||||
scale=0.9,
|
||||
text_scale=0.9,
|
||||
on_activate_call=self._save,
|
||||
)
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget,
|
||||
cancel_button=cancel_button,
|
||||
start_button=save_button,
|
||||
)
|
||||
|
||||
ba.widget(edit=cancel_button, right_widget=save_button)
|
||||
ba.widget(edit=save_button, left_widget=cancel_button)
|
||||
|
||||
v = self._height - 74.0
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, v + 15),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(
|
||||
resource=self._r + '.configuringText',
|
||||
subs=[('${DEVICE}', self._displayname)],
|
||||
),
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=270,
|
||||
scale=0.83,
|
||||
)
|
||||
v -= 20
|
||||
|
||||
if self._unique_id != '#1':
|
||||
v -= 20
|
||||
v -= self._spacing
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, v + 19),
|
||||
size=(self._width, 50),
|
||||
text=ba.Lstr(resource=self._r + '.keyboard2NoteText'),
|
||||
scale=0.7,
|
||||
maxwidth=self._width * 0.75,
|
||||
max_height=110,
|
||||
color=ba.app.ui.infotextcolor,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
)
|
||||
v -= 40
|
||||
v -= 10
|
||||
v -= self._spacing * 2.2
|
||||
v += 25
|
||||
v -= 42
|
||||
h_offs = 160
|
||||
dist = 70
|
||||
d_color = (0.4, 0.4, 0.8)
|
||||
self._capture_button(
|
||||
pos=(h_offs, v + 0.95 * dist),
|
||||
color=d_color,
|
||||
button='buttonUp',
|
||||
texture=ba.gettexture('upButton'),
|
||||
scale=1.0,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs - 1.2 * dist, v),
|
||||
color=d_color,
|
||||
button='buttonLeft',
|
||||
texture=ba.gettexture('leftButton'),
|
||||
scale=1.0,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs + 1.2 * dist, v),
|
||||
color=d_color,
|
||||
button='buttonRight',
|
||||
texture=ba.gettexture('rightButton'),
|
||||
scale=1.0,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs, v - 0.95 * dist),
|
||||
color=d_color,
|
||||
button='buttonDown',
|
||||
texture=ba.gettexture('downButton'),
|
||||
scale=1.0,
|
||||
)
|
||||
|
||||
if self._unique_id == '#2':
|
||||
self._capture_button(
|
||||
pos=(self._width * 0.5, v + 0.1 * dist),
|
||||
color=(0.4, 0.4, 0.6),
|
||||
button='buttonStart',
|
||||
texture=ba.gettexture('startButton'),
|
||||
scale=0.8,
|
||||
)
|
||||
|
||||
h_offs = self._width - 160
|
||||
|
||||
self._capture_button(
|
||||
pos=(h_offs, v + 0.95 * dist),
|
||||
color=(0.6, 0.4, 0.8),
|
||||
button='buttonPickUp',
|
||||
texture=ba.gettexture('buttonPickUp'),
|
||||
scale=1.0,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs - 1.2 * dist, v),
|
||||
color=(0.7, 0.5, 0.1),
|
||||
button='buttonPunch',
|
||||
texture=ba.gettexture('buttonPunch'),
|
||||
scale=1.0,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs + 1.2 * dist, v),
|
||||
color=(0.5, 0.2, 0.1),
|
||||
button='buttonBomb',
|
||||
texture=ba.gettexture('buttonBomb'),
|
||||
scale=1.0,
|
||||
)
|
||||
self._capture_button(
|
||||
pos=(h_offs, v - 0.95 * dist),
|
||||
color=(0.2, 0.5, 0.2),
|
||||
button='buttonJump',
|
||||
texture=ba.gettexture('buttonJump'),
|
||||
scale=1.0,
|
||||
)
|
||||
|
||||
def _capture_button(
|
||||
self,
|
||||
pos: tuple[float, float],
|
||||
color: tuple[float, float, float],
|
||||
texture: ba.Texture,
|
||||
button: str,
|
||||
scale: float = 1.0,
|
||||
) -> None:
|
||||
base_size = 79
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
position=(
|
||||
pos[0] - base_size * 0.5 * scale,
|
||||
pos[1] - base_size * 0.5 * scale,
|
||||
),
|
||||
size=(base_size * scale, base_size * scale),
|
||||
texture=texture,
|
||||
label='',
|
||||
color=color,
|
||||
)
|
||||
|
||||
# Do this deferred so it shows up on top of other buttons. (ew.)
|
||||
def doit() -> None:
|
||||
if not self._root_widget:
|
||||
return
|
||||
uiscale = 0.66 * scale * 2.0
|
||||
maxwidth = 76.0 * scale
|
||||
txt = ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(pos[0] + 0.0 * scale, pos[1] - (57.0 - 18.0) * scale),
|
||||
color=(1, 1, 1, 0.3),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
scale=uiscale,
|
||||
maxwidth=maxwidth,
|
||||
text=self._input.get_button_name(self._settings[button]),
|
||||
)
|
||||
ba.buttonwidget(
|
||||
edit=btn,
|
||||
autoselect=True,
|
||||
on_activate_call=ba.Call(
|
||||
AwaitKeyboardInputWindow, button, txt, self._settings
|
||||
),
|
||||
)
|
||||
|
||||
ba.pushcall(doit)
|
||||
|
||||
def _cancel(self) -> None:
|
||||
from bastd.ui.settings.controls import ControlsSettingsWindow
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
|
||||
def _save(self) -> None:
|
||||
from bastd.ui.settings.controls import ControlsSettingsWindow
|
||||
from ba.internal import (
|
||||
get_input_device_config,
|
||||
should_submit_debug_info,
|
||||
master_server_post,
|
||||
)
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
|
||||
# There's a chance the device disappeared; handle that gracefully.
|
||||
if not self._input:
|
||||
return
|
||||
|
||||
dst = get_input_device_config(self._input, default=False)
|
||||
dst2: dict[str, Any] = dst[0][dst[1]]
|
||||
dst2.clear()
|
||||
|
||||
# Store any values that aren't -1.
|
||||
for key, val in list(self._settings.items()):
|
||||
if val != -1:
|
||||
dst2[key] = val
|
||||
|
||||
# If we're allowed to phone home, send this config so we can generate
|
||||
# more defaults in the future.
|
||||
if should_submit_debug_info():
|
||||
master_server_post(
|
||||
'controllerConfig',
|
||||
{
|
||||
'ua': ba.app.user_agent_string,
|
||||
'name': self._name,
|
||||
'b': ba.app.build_number,
|
||||
'config': dst2,
|
||||
'v': 2,
|
||||
},
|
||||
)
|
||||
ba.app.config.apply_and_commit()
|
||||
ba.app.ui.set_main_menu_window(
|
||||
ControlsSettingsWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
|
||||
|
||||
class AwaitKeyboardInputWindow(ba.Window):
|
||||
"""Window for capturing a keypress."""
|
||||
|
||||
def __init__(self, button: str, ui: ba.Widget, settings: dict):
|
||||
|
||||
self._capture_button = button
|
||||
self._capture_key_ui = ui
|
||||
self._settings = settings
|
||||
|
||||
width = 400
|
||||
height = 150
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(width, height),
|
||||
transition='in_right',
|
||||
scale=(
|
||||
2.0
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.5
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
)
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, height - 60),
|
||||
size=(width, 25),
|
||||
text=ba.Lstr(resource='pressAnyKeyText'),
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
)
|
||||
|
||||
self._counter = 5
|
||||
self._count_down_text = ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
h_align='center',
|
||||
position=(0, height - 110),
|
||||
size=(width, 25),
|
||||
color=(1, 1, 1, 0.3),
|
||||
text=str(self._counter),
|
||||
)
|
||||
self._decrement_timer: ba.Timer | None = ba.Timer(
|
||||
1.0, self._decrement, repeat=True, timetype=ba.TimeType.REAL
|
||||
)
|
||||
ba.internal.capture_keyboard_input(ba.WeakCall(self._button_callback))
|
||||
|
||||
def __del__(self) -> None:
|
||||
ba.internal.release_keyboard_input()
|
||||
|
||||
def _die(self) -> None:
|
||||
# This strong-refs us; killing it allows us to die now.
|
||||
self._decrement_timer = None
|
||||
if self._root_widget:
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
|
||||
def _button_callback(self, event: dict[str, Any]) -> None:
|
||||
self._settings[self._capture_button] = event['button']
|
||||
if event['type'] == 'BUTTONDOWN':
|
||||
bname = event['input_device'].get_button_name(event['button'])
|
||||
ba.textwidget(edit=self._capture_key_ui, text=bname)
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
self._die()
|
||||
|
||||
def _decrement(self) -> None:
|
||||
self._counter -= 1
|
||||
if self._counter >= 1:
|
||||
ba.textwidget(edit=self._count_down_text, text=str(self._counter))
|
||||
else:
|
||||
self._die()
|
||||
448
dist/ba_data/python/bastd/ui/settings/nettesting.py
vendored
Normal file
448
dist/ba_data/python/bastd/ui/settings/nettesting.py
vendored
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides ui for network related testing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import copy
|
||||
import weakref
|
||||
from threading import Thread
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from efro.error import CleanError
|
||||
import ba
|
||||
import ba.internal
|
||||
from bastd.ui.settings.testing import TestingWindow
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Callable, Any
|
||||
|
||||
# We generally want all net tests to timeout on their own, but we add
|
||||
# sort of sane max in case they don't.
|
||||
MAX_TEST_SECONDS = 60 * 2
|
||||
|
||||
|
||||
class NetTestingWindow(ba.Window):
|
||||
"""Window that runs a networking test suite to help diagnose issues."""
|
||||
|
||||
def __init__(self, transition: str = 'in_right'):
|
||||
self._width = 820
|
||||
self._height = 500
|
||||
self._printed_lines: list[str] = []
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
scale=(
|
||||
1.56
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.2
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 0.8
|
||||
),
|
||||
stack_offset=(0.0, -7 if uiscale is ba.UIScale.SMALL else 0.0),
|
||||
transition=transition,
|
||||
)
|
||||
)
|
||||
self._done_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(40, self._height - 77),
|
||||
size=(120, 60),
|
||||
scale=0.8,
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='doneText'),
|
||||
on_activate_call=self._done,
|
||||
)
|
||||
|
||||
self._copy_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width - 200, self._height - 77),
|
||||
size=(100, 60),
|
||||
scale=0.8,
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='copyText'),
|
||||
on_activate_call=self._copy,
|
||||
)
|
||||
|
||||
self._settings_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width - 100, self._height - 77),
|
||||
size=(60, 60),
|
||||
scale=0.8,
|
||||
autoselect=True,
|
||||
label=ba.Lstr(value='...'),
|
||||
on_activate_call=self._show_val_testing,
|
||||
)
|
||||
|
||||
twidth = self._width - 450
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 55),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource='settingsWindowAdvanced.netTestingText'),
|
||||
color=(0.8, 0.8, 0.8, 1.0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=twidth,
|
||||
)
|
||||
|
||||
self._scroll = ba.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
position=(50, 50),
|
||||
size=(self._width - 100, self._height - 140),
|
||||
capture_arrows=True,
|
||||
autoselect=True,
|
||||
)
|
||||
self._rows = ba.columnwidget(parent=self._scroll)
|
||||
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._done_button
|
||||
)
|
||||
|
||||
# Now kick off the tests.
|
||||
# Pass a weak-ref to this window so we don't keep it alive
|
||||
# if we back out before it completes. Also set is as daemon
|
||||
# so it doesn't keep the app running if the user is trying to quit.
|
||||
Thread(
|
||||
daemon=True,
|
||||
target=ba.Call(_run_diagnostics, weakref.ref(self)),
|
||||
).start()
|
||||
|
||||
def print(self, text: str, color: tuple[float, float, float]) -> None:
|
||||
"""Print text to our console thingie."""
|
||||
for line in text.splitlines():
|
||||
txt = ba.textwidget(
|
||||
parent=self._rows,
|
||||
color=color,
|
||||
text=line,
|
||||
scale=0.75,
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
size=(0, 20),
|
||||
)
|
||||
ba.containerwidget(edit=self._rows, visible_child=txt)
|
||||
self._printed_lines.append(line)
|
||||
|
||||
def _copy(self) -> None:
|
||||
if not ba.clipboard_is_supported():
|
||||
ba.screenmessage(
|
||||
'Clipboard not supported on this platform.', color=(1, 0, 0)
|
||||
)
|
||||
return
|
||||
ba.clipboard_set_text('\n'.join(self._printed_lines))
|
||||
ba.screenmessage(f'{len(self._printed_lines)} lines copied.')
|
||||
|
||||
def _show_val_testing(self) -> None:
|
||||
ba.app.ui.set_main_menu_window(NetValTestingWindow().get_root_widget())
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
|
||||
def _done(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
ba.app.ui.set_main_menu_window(
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
|
||||
|
||||
def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
|
||||
# pylint: disable=too-many-statements
|
||||
|
||||
from efro.util import utc_now
|
||||
|
||||
have_error = [False]
|
||||
|
||||
# We're running in a background thread but UI stuff needs to run
|
||||
# in the logic thread; give ourself a way to pass stuff to it.
|
||||
def _print(
|
||||
text: str, color: tuple[float, float, float] | None = None
|
||||
) -> None:
|
||||
def _print_in_logic_thread() -> None:
|
||||
win = weakwin()
|
||||
if win is not None:
|
||||
win.print(text, (1.0, 1.0, 1.0) if color is None else color)
|
||||
|
||||
ba.pushcall(_print_in_logic_thread, from_other_thread=True)
|
||||
|
||||
def _print_test_results(call: Callable[[], Any]) -> bool:
|
||||
"""Run the provided call, print result, & return success."""
|
||||
starttime = time.monotonic()
|
||||
try:
|
||||
call()
|
||||
duration = time.monotonic() - starttime
|
||||
_print(f'Succeeded in {duration:.2f}s.', color=(0, 1, 0))
|
||||
return True
|
||||
except Exception as exc:
|
||||
import traceback
|
||||
|
||||
duration = time.monotonic() - starttime
|
||||
msg = (
|
||||
str(exc)
|
||||
if isinstance(exc, CleanError)
|
||||
else traceback.format_exc()
|
||||
)
|
||||
_print(msg, color=(1.0, 1.0, 0.3))
|
||||
_print(f'Failed in {duration:.2f}s.', color=(1, 0, 0))
|
||||
have_error[0] = True
|
||||
return False
|
||||
|
||||
try:
|
||||
_print(
|
||||
f'Running network diagnostics...\n'
|
||||
f'ua: {ba.app.user_agent_string}\n'
|
||||
f'time: {utc_now()}.'
|
||||
)
|
||||
|
||||
if bool(False):
|
||||
_print('\nRunning dummy success test...')
|
||||
_print_test_results(_dummy_success)
|
||||
|
||||
_print('\nRunning dummy fail test...')
|
||||
_print_test_results(_dummy_fail)
|
||||
|
||||
# V1 ping
|
||||
baseaddr = ba.internal.get_master_server_address(source=0, version=1)
|
||||
_print(f'\nContacting V1 master-server src0 ({baseaddr})...')
|
||||
v1worked = _print_test_results(lambda: _test_fetch(baseaddr))
|
||||
|
||||
# V1 alternate ping (only if primary fails since this often fails).
|
||||
if v1worked:
|
||||
_print('\nSkipping V1 master-server src1 test since src0 worked.')
|
||||
else:
|
||||
baseaddr = ba.internal.get_master_server_address(
|
||||
source=1, version=1
|
||||
)
|
||||
_print(f'\nContacting V1 master-server src1 ({baseaddr})...')
|
||||
_print_test_results(lambda: _test_fetch(baseaddr))
|
||||
|
||||
if 'none succeeded' in ba.app.net.v1_test_log:
|
||||
_print(
|
||||
f'\nV1-test-log failed: {ba.app.net.v1_test_log}',
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
have_error[0] = True
|
||||
else:
|
||||
_print(f'\nV1-test-log ok: {ba.app.net.v1_test_log}')
|
||||
|
||||
for srcid, result in sorted(ba.app.net.v1_ctest_results.items()):
|
||||
_print(f'\nV1 src{srcid} result: {result}')
|
||||
|
||||
curv1addr = ba.internal.get_master_server_address(version=1)
|
||||
_print(f'\nUsing V1 address: {curv1addr}')
|
||||
|
||||
_print('\nRunning V1 transaction...')
|
||||
_print_test_results(_test_v1_transaction)
|
||||
|
||||
# V2 ping
|
||||
baseaddr = ba.internal.get_master_server_address(version=2)
|
||||
_print(f'\nContacting V2 master-server ({baseaddr})...')
|
||||
_print_test_results(lambda: _test_fetch(baseaddr))
|
||||
|
||||
_print('\nComparing local time to V2 server...')
|
||||
_print_test_results(_test_v2_time)
|
||||
|
||||
# Get V2 nearby zone
|
||||
with ba.app.net.zone_pings_lock:
|
||||
zone_pings = copy.deepcopy(ba.app.net.zone_pings)
|
||||
nearest_zone = (
|
||||
None
|
||||
if not zone_pings
|
||||
else sorted(zone_pings.items(), key=lambda i: i[1])[0]
|
||||
)
|
||||
|
||||
if nearest_zone is not None:
|
||||
nearstr = f'{nearest_zone[0]}: {nearest_zone[1]:.0f}ms'
|
||||
else:
|
||||
nearstr = '-'
|
||||
_print(f'\nChecking nearest V2 zone ping ({nearstr})...')
|
||||
_print_test_results(lambda: _test_nearby_zone_ping(nearest_zone))
|
||||
|
||||
_print('\nSending V2 cloud message...')
|
||||
_print_test_results(_test_v2_cloud_message)
|
||||
|
||||
if have_error[0]:
|
||||
_print(
|
||||
'\nDiagnostics complete. Some diagnostics failed.',
|
||||
color=(10, 0, 0),
|
||||
)
|
||||
else:
|
||||
_print(
|
||||
'\nDiagnostics complete. Everything looks good!',
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
except Exception:
|
||||
import traceback
|
||||
|
||||
_print(
|
||||
f'An unexpected error occurred during testing;'
|
||||
f' please report this.\n'
|
||||
f'{traceback.format_exc()}',
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
|
||||
|
||||
def _dummy_success() -> None:
|
||||
"""Dummy success test."""
|
||||
time.sleep(1.2)
|
||||
|
||||
|
||||
def _dummy_fail() -> None:
|
||||
"""Dummy fail test case."""
|
||||
raise RuntimeError('fail-test')
|
||||
|
||||
|
||||
def _test_v1_transaction() -> None:
|
||||
"""Dummy fail test case."""
|
||||
if ba.internal.get_v1_account_state() != 'signed_in':
|
||||
raise RuntimeError('Not signed in.')
|
||||
|
||||
starttime = time.monotonic()
|
||||
|
||||
# Gets set to True on success or string on error.
|
||||
results: list[Any] = [False]
|
||||
|
||||
def _cb(cbresults: Any) -> None:
|
||||
# Simply set results here; our other thread acts on them.
|
||||
if not isinstance(cbresults, dict) or 'party_code' not in cbresults:
|
||||
results[0] = 'Unexpected transaction response'
|
||||
return
|
||||
results[0] = True # Success!
|
||||
|
||||
def _do_it() -> None:
|
||||
# Fire off a transaction with a callback.
|
||||
ba.internal.add_transaction(
|
||||
{
|
||||
'type': 'PRIVATE_PARTY_QUERY',
|
||||
'expire_time': time.time() + 20,
|
||||
},
|
||||
callback=_cb,
|
||||
)
|
||||
ba.internal.run_transactions()
|
||||
|
||||
ba.pushcall(_do_it, from_other_thread=True)
|
||||
|
||||
while results[0] is False:
|
||||
time.sleep(0.01)
|
||||
if time.monotonic() - starttime > MAX_TEST_SECONDS:
|
||||
raise RuntimeError(
|
||||
f'test timed out after {MAX_TEST_SECONDS} seconds'
|
||||
)
|
||||
|
||||
# If we got left a string, its an error.
|
||||
if isinstance(results[0], str):
|
||||
raise RuntimeError(results[0])
|
||||
|
||||
|
||||
def _test_v2_cloud_message() -> None:
|
||||
from dataclasses import dataclass
|
||||
import bacommon.cloud
|
||||
|
||||
@dataclass
|
||||
class _Results:
|
||||
errstr: str | None = None
|
||||
send_time: float | None = None
|
||||
response_time: float | None = None
|
||||
|
||||
results = _Results()
|
||||
|
||||
def _cb(response: bacommon.cloud.PingResponse | Exception) -> None:
|
||||
# Note: this runs in another thread so need to avoid exceptions.
|
||||
results.response_time = time.monotonic()
|
||||
if isinstance(response, Exception):
|
||||
results.errstr = str(response)
|
||||
if not isinstance(response, bacommon.cloud.PingResponse):
|
||||
results.errstr = f'invalid response type: {type(response)}.'
|
||||
|
||||
def _send() -> None:
|
||||
# Note: this runs in another thread so need to avoid exceptions.
|
||||
results.send_time = time.monotonic()
|
||||
ba.app.cloud.send_message_cb(bacommon.cloud.PingMessage(), _cb)
|
||||
|
||||
# This stuff expects to be run from the logic thread.
|
||||
ba.pushcall(_send, from_other_thread=True)
|
||||
|
||||
wait_start_time = time.monotonic()
|
||||
while True:
|
||||
if results.response_time is not None:
|
||||
break
|
||||
time.sleep(0.01)
|
||||
if time.monotonic() - wait_start_time > MAX_TEST_SECONDS:
|
||||
raise RuntimeError(
|
||||
f'Timeout ({MAX_TEST_SECONDS} seconds)'
|
||||
f' waiting for cloud message response'
|
||||
)
|
||||
if results.errstr is not None:
|
||||
raise RuntimeError(results.errstr)
|
||||
|
||||
|
||||
def _test_v2_time() -> None:
|
||||
offset = ba.app.net.server_time_offset_hours
|
||||
if offset is None:
|
||||
raise RuntimeError(
|
||||
'no time offset found;'
|
||||
' perhaps unable to communicate with v2 server?'
|
||||
)
|
||||
if abs(offset) >= 2.0:
|
||||
raise CleanError(
|
||||
f'Your device time is off from world time by {offset:.1f} hours.\n'
|
||||
'This may cause network operations to fail due to your device\n'
|
||||
' incorrectly treating SSL certificates as not-yet-valid, etc.\n'
|
||||
'Check your device time and time-zone settings to fix this.\n'
|
||||
)
|
||||
|
||||
|
||||
def _test_fetch(baseaddr: str) -> None:
|
||||
# pylint: disable=consider-using-with
|
||||
import urllib.request
|
||||
|
||||
response = urllib.request.urlopen(
|
||||
urllib.request.Request(
|
||||
f'{baseaddr}/ping', None, {'User-Agent': ba.app.user_agent_string}
|
||||
),
|
||||
context=ba.app.net.sslcontext,
|
||||
timeout=MAX_TEST_SECONDS,
|
||||
)
|
||||
if response.getcode() != 200:
|
||||
raise RuntimeError(
|
||||
f'Got unexpected response code {response.getcode()}.'
|
||||
)
|
||||
data = response.read()
|
||||
if data != b'pong':
|
||||
raise RuntimeError('Got unexpected response data.')
|
||||
|
||||
|
||||
def _test_nearby_zone_ping(nearest_zone: tuple[str, float] | None) -> None:
|
||||
"""Try to ping nearest v2 zone."""
|
||||
if nearest_zone is None:
|
||||
raise RuntimeError('No nearest zone.')
|
||||
if nearest_zone[1] > 500:
|
||||
raise RuntimeError('Ping too high.')
|
||||
|
||||
|
||||
class NetValTestingWindow(TestingWindow):
|
||||
"""Window to test network related settings."""
|
||||
|
||||
def __init__(self, transition: str = 'in_right'):
|
||||
|
||||
entries = [
|
||||
{'name': 'bufferTime', 'label': 'Buffer Time', 'increment': 1.0},
|
||||
{
|
||||
'name': 'delaySampling',
|
||||
'label': 'Delay Sampling',
|
||||
'increment': 1.0,
|
||||
},
|
||||
{
|
||||
'name': 'dynamicsSyncTime',
|
||||
'label': 'Dynamics Sync Time',
|
||||
'increment': 10,
|
||||
},
|
||||
{'name': 'showNetInfo', 'label': 'Show Net Info', 'increment': 1},
|
||||
]
|
||||
super().__init__(
|
||||
title=ba.Lstr(resource='settingsWindowAdvanced.netTestingText'),
|
||||
entries=entries,
|
||||
transition=transition,
|
||||
back_call=lambda: NetTestingWindow(transition='in_left'),
|
||||
)
|
||||
264
dist/ba_data/python/bastd/ui/settings/plugins.py
vendored
Normal file
264
dist/ba_data/python/bastd/ui/settings/plugins.py
vendored
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Plugin Window UI."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class PluginWindow(ba.Window):
|
||||
"""Window for configuring plugins."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str = 'in_right',
|
||||
origin_widget: ba.Widget | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
app = ba.app
|
||||
|
||||
# If they provided an origin-widget, scale up from that.
|
||||
scale_origin: tuple[float, float] | None
|
||||
if origin_widget is not None:
|
||||
self._transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
|
||||
uiscale = ba.app.ui.uiscale
|
||||
self._width = 870.0 if uiscale is ba.UIScale.SMALL else 670.0
|
||||
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
|
||||
self._height = (
|
||||
390.0
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 450.0
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 520.0
|
||||
)
|
||||
top_extra = 10 if uiscale is ba.UIScale.SMALL else 0
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height + top_extra),
|
||||
transition=transition,
|
||||
toolbar_visibility='menu_minimal',
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
scale=(
|
||||
2.06
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.4
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(0, -25)
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else (0, 0),
|
||||
)
|
||||
)
|
||||
|
||||
self._scroll_width = self._width - (100 + 2 * x_inset)
|
||||
self._scroll_height = self._height - 115.0
|
||||
self._sub_width = self._scroll_width * 0.95
|
||||
self._sub_height = 724.0
|
||||
|
||||
if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=self._do_back
|
||||
)
|
||||
self._back_button = None
|
||||
else:
|
||||
self._back_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(53 + x_inset, self._height - 60),
|
||||
size=(140, 60),
|
||||
scale=0.8,
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
on_activate_call=self._do_back,
|
||||
)
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._back_button
|
||||
)
|
||||
|
||||
self._title_text = ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, self._height - 52),
|
||||
size=(self._width, 25),
|
||||
text=ba.Lstr(resource='pluginsText'),
|
||||
color=app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
)
|
||||
|
||||
if self._back_button is not None:
|
||||
ba.buttonwidget(
|
||||
edit=self._back_button,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
settings_button_x = 670 if uiscale is ba.UIScale.SMALL else 570
|
||||
self._settings_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(settings_button_x, self._height - 60),
|
||||
size=(40, 40),
|
||||
label='',
|
||||
on_activate_call=self._open_settings,
|
||||
)
|
||||
|
||||
ba.imagewidget(
|
||||
parent=self._root_widget,
|
||||
position=(settings_button_x + 3, self._height - 60),
|
||||
size=(35, 35),
|
||||
texture=ba.gettexture('settingsIcon'),
|
||||
)
|
||||
|
||||
ba.widget(
|
||||
edit=self._settings_button,
|
||||
up_widget=self._settings_button,
|
||||
right_widget=self._settings_button,
|
||||
)
|
||||
|
||||
self._scrollwidget = ba.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
position=(50 + x_inset, 50),
|
||||
simple_culling_v=20.0,
|
||||
highlight=False,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
selection_loops_to_parent=True,
|
||||
claims_left_right=True,
|
||||
)
|
||||
ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
|
||||
|
||||
if ba.app.meta.scanresults is None:
|
||||
ba.screenmessage(
|
||||
'Still scanning plugins; please try again.', color=(1, 0, 0)
|
||||
)
|
||||
ba.playsound(ba.getsound('error'))
|
||||
pluglist = ba.app.plugins.potential_plugins
|
||||
plugstates: dict[str, dict] = ba.app.config.setdefault('Plugins', {})
|
||||
assert isinstance(plugstates, dict)
|
||||
|
||||
plug_line_height = 50
|
||||
sub_width = self._scroll_width
|
||||
sub_height = len(pluglist) * plug_line_height
|
||||
self._subcontainer = ba.containerwidget(
|
||||
parent=self._scrollwidget,
|
||||
size=(sub_width, sub_height),
|
||||
background=False,
|
||||
)
|
||||
|
||||
for i, availplug in enumerate(pluglist):
|
||||
plugin = ba.app.plugins.active_plugins.get(availplug.class_path)
|
||||
active = plugin is not None
|
||||
|
||||
plugstate = plugstates.setdefault(availplug.class_path, {})
|
||||
checked = plugstate.get('enabled', False)
|
||||
assert isinstance(checked, bool)
|
||||
|
||||
item_y = sub_height - (i + 1) * plug_line_height
|
||||
check = ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
text=availplug.display_name,
|
||||
autoselect=True,
|
||||
value=checked,
|
||||
maxwidth=self._scroll_width - 200,
|
||||
position=(10, item_y),
|
||||
size=(self._scroll_width - 40, 50),
|
||||
on_value_change_call=ba.Call(
|
||||
self._check_value_changed, availplug
|
||||
),
|
||||
textcolor=(
|
||||
(0.8, 0.3, 0.3)
|
||||
if not availplug.available
|
||||
else (0, 1, 0)
|
||||
if active
|
||||
else (0.6, 0.6, 0.6)
|
||||
),
|
||||
)
|
||||
if plugin is not None and plugin.has_settings_ui():
|
||||
button = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
label=ba.Lstr(resource='mainMenu.settingsText'),
|
||||
autoselect=True,
|
||||
size=(100, 40),
|
||||
position=(sub_width - 130, item_y + 6),
|
||||
)
|
||||
ba.buttonwidget(
|
||||
edit=button,
|
||||
on_activate_call=ba.Call(plugin.show_settings_ui, button),
|
||||
)
|
||||
else:
|
||||
button = None
|
||||
|
||||
# Allow getting back to back button.
|
||||
if i == 0:
|
||||
ba.widget(
|
||||
edit=check,
|
||||
up_widget=self._back_button,
|
||||
left_widget=self._back_button,
|
||||
right_widget=self._settings_button,
|
||||
)
|
||||
if button is not None:
|
||||
ba.widget(edit=button, up_widget=self._back_button)
|
||||
|
||||
# Make sure we scroll all the way to the end when using
|
||||
# keyboard/button nav.
|
||||
ba.widget(edit=check, show_buffer_top=40, show_buffer_bottom=40)
|
||||
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._scrollwidget
|
||||
)
|
||||
|
||||
self._restore_state()
|
||||
|
||||
def _check_value_changed(
|
||||
self, plug: ba.PotentialPlugin, value: bool
|
||||
) -> None:
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
|
||||
color=(1.0, 0.5, 0.0),
|
||||
)
|
||||
plugstates: dict[str, dict] = ba.app.config.setdefault('Plugins', {})
|
||||
assert isinstance(plugstates, dict)
|
||||
plugstate = plugstates.setdefault(plug.class_path, {})
|
||||
plugstate['enabled'] = value
|
||||
ba.app.config.commit()
|
||||
|
||||
def _open_settings(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.pluginsettings import PluginSettingsWindow
|
||||
|
||||
ba.playsound(ba.getsound('swish'))
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
PluginSettingsWindow(transition='in_right').get_root_widget()
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
pass
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
pass
|
||||
|
||||
def _do_back(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
self._save_state()
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
ba.app.ui.set_main_menu_window(
|
||||
AdvancedSettingsWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
174
dist/ba_data/python/bastd/ui/settings/pluginsettings.py
vendored
Normal file
174
dist/ba_data/python/bastd/ui/settings/pluginsettings.py
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Plugin Settings UI."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
from bastd.ui.confirm import ConfirmWindow
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class PluginSettingsWindow(ba.Window):
|
||||
"""Plugin Settings Window"""
|
||||
|
||||
def __init__(self, transition: str = 'in_right'):
|
||||
|
||||
scale_origin: tuple[float, float] | None
|
||||
self._transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
|
||||
uiscale = ba.app.ui.uiscale
|
||||
width = 470.0 if uiscale is ba.UIScale.SMALL else 470.0
|
||||
height = (
|
||||
365.0
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 300.0
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 370.0
|
||||
)
|
||||
top_extra = 10 if uiscale is ba.UIScale.SMALL else 0
|
||||
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(width, height + top_extra),
|
||||
transition=transition,
|
||||
toolbar_visibility='menu_minimal',
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
scale=(
|
||||
2.06
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.4
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(0, -25)
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else (0, 0),
|
||||
)
|
||||
)
|
||||
|
||||
self._back_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(53, height - 60),
|
||||
size=(60, 60),
|
||||
scale=0.8,
|
||||
autoselect=True,
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
button_type='backSmall',
|
||||
on_activate_call=self._do_back,
|
||||
)
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._back_button
|
||||
)
|
||||
|
||||
self._title_text = ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(0, height - 52),
|
||||
size=(width, 25),
|
||||
text=ba.Lstr(resource='pluginSettingsText'),
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
)
|
||||
|
||||
self._y_position = 170 if uiscale is ba.UIScale.MEDIUM else 205
|
||||
self._enable_plugins_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(65, self._y_position),
|
||||
size=(350, 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='pluginsEnableAllText'),
|
||||
text_scale=1.0,
|
||||
on_activate_call=lambda: ConfirmWindow(
|
||||
action=self._enable_all_plugins,
|
||||
),
|
||||
)
|
||||
|
||||
self._y_position -= 70
|
||||
self._disable_plugins_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(65, self._y_position),
|
||||
size=(350, 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='pluginsDisableAllText'),
|
||||
text_scale=1.0,
|
||||
on_activate_call=lambda: ConfirmWindow(
|
||||
action=self._disable_all_plugins,
|
||||
),
|
||||
)
|
||||
|
||||
self._y_position -= 70
|
||||
self._enable_new_plugins_check_box = ba.checkboxwidget(
|
||||
parent=self._root_widget,
|
||||
position=(65, self._y_position),
|
||||
size=(350, 60),
|
||||
value=ba.app.config.get(
|
||||
ba.app.plugins.AUTO_ENABLE_NEW_PLUGINS_CONFIG_KEY,
|
||||
ba.app.plugins.AUTO_ENABLE_NEW_PLUGINS_DEFAULT,
|
||||
),
|
||||
text=ba.Lstr(resource='pluginsAutoEnableNewText'),
|
||||
scale=1.0,
|
||||
maxwidth=308,
|
||||
on_value_change_call=self._update_value,
|
||||
)
|
||||
|
||||
ba.widget(
|
||||
edit=self._back_button, down_widget=self._enable_plugins_button
|
||||
)
|
||||
|
||||
ba.widget(
|
||||
edit=self._disable_plugins_button,
|
||||
left_widget=self._disable_plugins_button,
|
||||
)
|
||||
|
||||
ba.widget(
|
||||
edit=self._enable_new_plugins_check_box,
|
||||
left_widget=self._enable_new_plugins_check_box,
|
||||
right_widget=self._enable_new_plugins_check_box,
|
||||
down_widget=self._enable_new_plugins_check_box,
|
||||
)
|
||||
|
||||
def _enable_all_plugins(self) -> None:
|
||||
cfg = ba.app.config
|
||||
plugs: dict[str, dict] = cfg.setdefault('Plugins', {})
|
||||
for plug in plugs.values():
|
||||
plug['enabled'] = True
|
||||
cfg.apply_and_commit()
|
||||
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
|
||||
color=(1.0, 0.5, 0.0),
|
||||
)
|
||||
|
||||
def _disable_all_plugins(self) -> None:
|
||||
cfg = ba.app.config
|
||||
plugs: dict[str, dict] = cfg.setdefault('Plugins', {})
|
||||
for plug in plugs.values():
|
||||
plug['enabled'] = False
|
||||
cfg.apply_and_commit()
|
||||
|
||||
ba.screenmessage(
|
||||
ba.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
|
||||
color=(1.0, 0.5, 0.0),
|
||||
)
|
||||
|
||||
def _update_value(self, val: bool) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg[ba.app.plugins.AUTO_ENABLE_NEW_PLUGINS_CONFIG_KEY] = val
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _do_back(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.plugins import PluginWindow
|
||||
|
||||
ba.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
ba.app.ui.set_main_menu_window(
|
||||
PluginWindow(transition='in_left').get_root_widget()
|
||||
)
|
||||
107
dist/ba_data/python/bastd/ui/settings/ps3controller.py
vendored
Normal file
107
dist/ba_data/python/bastd/ui/settings/ps3controller.py
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Settings UI related to PS3 controllers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import _ba
|
||||
import ba
|
||||
|
||||
|
||||
class PS3ControllerSettingsWindow(ba.Window):
|
||||
"""UI showing info about using PS3 controllers."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
width = 760
|
||||
height = 330 if _ba.is_running_on_fire_tv() else 540
|
||||
spacing = 40
|
||||
self._r = 'ps3ControllersWindow'
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(root_widget=ba.containerwidget(
|
||||
size=(width, height),
|
||||
transition='in_right',
|
||||
scale=(1.35 if uiscale is ba.UIScale.SMALL else
|
||||
1.3 if uiscale is ba.UIScale.MEDIUM else 1.0)))
|
||||
|
||||
btn = ba.buttonwidget(parent=self._root_widget,
|
||||
position=(37, height - 73),
|
||||
size=(135, 65),
|
||||
scale=0.85,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
autoselect=True,
|
||||
on_activate_call=self._back)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(width * 0.5, height - 46),
|
||||
size=(0, 0),
|
||||
maxwidth=410,
|
||||
text=ba.Lstr(resource=self._r + '.titleText',
|
||||
subs=[('${APP_NAME}',
|
||||
ba.Lstr(resource='titleText'))]),
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='center')
|
||||
|
||||
ba.buttonwidget(edit=btn,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK))
|
||||
|
||||
v = height - 90
|
||||
v -= spacing
|
||||
|
||||
if _ba.is_running_on_fire_tv():
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(width * 0.5, height * 0.45),
|
||||
size=(0, 0),
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
maxwidth=width * 0.95,
|
||||
max_height=height * 0.8,
|
||||
scale=1.0,
|
||||
text=ba.Lstr(resource=self._r +
|
||||
'.ouyaInstructionsText'),
|
||||
h_align='center',
|
||||
v_align='center')
|
||||
else:
|
||||
txts = ba.Lstr(resource=self._r +
|
||||
'.macInstructionsText').evaluate().split('\n\n\n')
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(width * 0.5, v - 29),
|
||||
size=(0, 0),
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
maxwidth=width * 0.95,
|
||||
max_height=170,
|
||||
scale=1.0,
|
||||
text=txts[0].strip(),
|
||||
h_align='center',
|
||||
v_align='center')
|
||||
if txts:
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(width * 0.5, v - 280),
|
||||
size=(0, 0),
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
maxwidth=width * 0.95,
|
||||
max_height=170,
|
||||
scale=1.0,
|
||||
text=txts[1].strip(),
|
||||
h_align='center',
|
||||
v_align='center')
|
||||
|
||||
ba.buttonwidget(parent=self._root_widget,
|
||||
position=(225, v - 176),
|
||||
size=(300, 40),
|
||||
label=ba.Lstr(resource=self._r +
|
||||
'.pairingTutorialText'),
|
||||
autoselect=True,
|
||||
on_activate_call=ba.Call(
|
||||
ba.open_url, 'http://www.youtube.com/watch'
|
||||
'?v=IlR_HxeOQpI&feature=related'))
|
||||
|
||||
def _back(self) -> None:
|
||||
from bastd.ui.settings import controls
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
controls.ControlsSettingsWindow(
|
||||
transition='in_left').get_root_widget())
|
||||
147
dist/ba_data/python/bastd/ui/settings/remoteapp.py
vendored
Normal file
147
dist/ba_data/python/bastd/ui/settings/remoteapp.py
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Settings UI functionality related to the remote app."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ba
|
||||
|
||||
|
||||
class RemoteAppSettingsWindow(ba.Window):
|
||||
"""Window showing info/settings related to the remote app."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
from ba.internal import get_remote_app_name
|
||||
|
||||
self._r = 'connectMobileDevicesWindow'
|
||||
width = 700
|
||||
height = 390
|
||||
spacing = 40
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(width, height),
|
||||
transition='in_right',
|
||||
scale=(
|
||||
1.85
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.3
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(-10, 0)
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else (0, 0),
|
||||
)
|
||||
)
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(40, height - 67),
|
||||
size=(140, 65),
|
||||
scale=0.8,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
text_scale=1.1,
|
||||
autoselect=True,
|
||||
on_activate_call=self._back,
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, height - 42),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.titleText'),
|
||||
maxwidth=370,
|
||||
color=ba.app.ui.title_color,
|
||||
scale=0.8,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
ba.buttonwidget(
|
||||
edit=btn,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
v = height - 70.0
|
||||
v -= spacing * 1.2
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(15, v - 26),
|
||||
size=(width - 30, 30),
|
||||
maxwidth=width * 0.95,
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
scale=0.8,
|
||||
text=ba.Lstr(
|
||||
resource=self._r + '.explanationText',
|
||||
subs=[
|
||||
('${APP_NAME}', ba.Lstr(resource='titleText')),
|
||||
('${REMOTE_APP_NAME}', get_remote_app_name()),
|
||||
],
|
||||
),
|
||||
max_height=100,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= 90
|
||||
|
||||
# hmm the itms:// version doesnt bounce through safari but is kinda
|
||||
# apple-specific-ish
|
||||
|
||||
# Update: now we just show link to the remote webpage.
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, v + 5),
|
||||
size=(0, 0),
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
scale=1.4,
|
||||
text='bombsquadgame.com/remote',
|
||||
maxwidth=width * 0.95,
|
||||
max_height=60,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= 30
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, v - 35),
|
||||
size=(0, 0),
|
||||
color=(0.7, 0.9, 0.7, 0.8),
|
||||
scale=0.65,
|
||||
text=ba.Lstr(resource=self._r + '.bestResultsText'),
|
||||
maxwidth=width * 0.95,
|
||||
max_height=height * 0.19,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
ba.checkboxwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5 - 150, v - 116),
|
||||
size=(300, 30),
|
||||
maxwidth=300,
|
||||
scale=0.8,
|
||||
value=not ba.app.config.resolve('Enable Remote App'),
|
||||
autoselect=True,
|
||||
text=ba.Lstr(resource='disableRemoteAppConnectionsText'),
|
||||
on_value_change_call=self._on_check_changed,
|
||||
)
|
||||
|
||||
def _on_check_changed(self, value: bool) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg['Enable Remote App'] = not value
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _back(self) -> None:
|
||||
from bastd.ui.settings import controls
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
controls.ControlsSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
)
|
||||
224
dist/ba_data/python/bastd/ui/settings/testing.py
vendored
Normal file
224
dist/ba_data/python/bastd/ui/settings/testing.py
vendored
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides UI for test settings."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
class TestingWindow(ba.Window):
|
||||
"""Window for conveniently testing various settings."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: ba.Lstr,
|
||||
entries: list[dict[str, Any]],
|
||||
transition: str = 'in_right',
|
||||
back_call: Callable[[], ba.Window] | None = None,
|
||||
):
|
||||
uiscale = ba.app.ui.uiscale
|
||||
self._width = 600
|
||||
self._height = 324 if uiscale is ba.UIScale.SMALL else 400
|
||||
self._entries = copy.deepcopy(entries)
|
||||
self._back_call = back_call
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
transition=transition,
|
||||
scale=(
|
||||
2.5
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.2
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
stack_offset=(0, -28)
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else (0, 0),
|
||||
)
|
||||
)
|
||||
self._back_button = btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
autoselect=True,
|
||||
position=(65, self._height - 59),
|
||||
size=(130, 60),
|
||||
scale=0.8,
|
||||
text_scale=1.2,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
on_activate_call=self._do_back,
|
||||
)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 35),
|
||||
size=(0, 0),
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=245,
|
||||
text=title,
|
||||
)
|
||||
|
||||
ba.buttonwidget(
|
||||
edit=self._back_button,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 75),
|
||||
size=(0, 0),
|
||||
color=ba.app.ui.infotextcolor,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=self._width * 0.75,
|
||||
text=ba.Lstr(resource='settingsWindowAdvanced.forTestingText'),
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
self._scroll_width = self._width - 130
|
||||
self._scroll_height = self._height - 140
|
||||
self._scrollwidget = ba.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
highlight=False,
|
||||
position=((self._width - self._scroll_width) * 0.5, 40),
|
||||
)
|
||||
ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
|
||||
|
||||
self._spacing = 50
|
||||
|
||||
self._sub_width = self._scroll_width * 0.95
|
||||
self._sub_height = 50 + len(self._entries) * self._spacing + 60
|
||||
self._subcontainer = ba.containerwidget(
|
||||
parent=self._scrollwidget,
|
||||
size=(self._sub_width, self._sub_height),
|
||||
background=False,
|
||||
)
|
||||
|
||||
h = 230
|
||||
v = self._sub_height - 48
|
||||
|
||||
for i, entry in enumerate(self._entries):
|
||||
|
||||
entry_name = entry['name']
|
||||
|
||||
# If we haven't yet, record the default value for this name so
|
||||
# we can reset if we want..
|
||||
if entry_name not in ba.app.value_test_defaults:
|
||||
ba.app.value_test_defaults[entry_name] = ba.internal.value_test(
|
||||
entry_name
|
||||
)
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h, v),
|
||||
size=(0, 0),
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
maxwidth=200,
|
||||
text=entry['label'],
|
||||
)
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h + 20, v - 19),
|
||||
size=(40, 40),
|
||||
autoselect=True,
|
||||
repeat=True,
|
||||
left_widget=self._back_button,
|
||||
button_type='square',
|
||||
label='-',
|
||||
on_activate_call=ba.Call(self._on_minus_press, entry['name']),
|
||||
)
|
||||
if i == 0:
|
||||
ba.widget(edit=btn, up_widget=self._back_button)
|
||||
# pylint: disable=consider-using-f-string
|
||||
entry['widget'] = ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h + 100, v),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=60,
|
||||
text='%.4g' % ba.internal.value_test(entry_name),
|
||||
)
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h + 140, v - 19),
|
||||
size=(40, 40),
|
||||
autoselect=True,
|
||||
repeat=True,
|
||||
button_type='square',
|
||||
label='+',
|
||||
on_activate_call=ba.Call(self._on_plus_press, entry['name']),
|
||||
)
|
||||
if i == 0:
|
||||
ba.widget(edit=btn, up_widget=self._back_button)
|
||||
v -= self._spacing
|
||||
v -= 35
|
||||
ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
autoselect=True,
|
||||
size=(200, 50),
|
||||
position=(self._sub_width * 0.5 - 100, v),
|
||||
label=ba.Lstr(resource='settingsWindowAdvanced.resetText'),
|
||||
right_widget=btn,
|
||||
on_activate_call=self._on_reset_press,
|
||||
)
|
||||
|
||||
def _get_entry(self, name: str) -> dict[str, Any]:
|
||||
for entry in self._entries:
|
||||
if entry['name'] == name:
|
||||
return entry
|
||||
raise ba.NotFoundError(f'Entry not found: {name}')
|
||||
|
||||
def _on_reset_press(self) -> None:
|
||||
for entry in self._entries:
|
||||
ba.internal.value_test(
|
||||
entry['name'],
|
||||
absolute=ba.app.value_test_defaults[entry['name']],
|
||||
)
|
||||
# pylint: disable=consider-using-f-string
|
||||
ba.textwidget(
|
||||
edit=entry['widget'],
|
||||
text='%.4g' % ba.internal.value_test(entry['name']),
|
||||
)
|
||||
|
||||
def _on_minus_press(self, entry_name: str) -> None:
|
||||
entry = self._get_entry(entry_name)
|
||||
ba.internal.value_test(entry['name'], change=-entry['increment'])
|
||||
# pylint: disable=consider-using-f-string
|
||||
ba.textwidget(
|
||||
edit=entry['widget'],
|
||||
text='%.4g' % ba.internal.value_test(entry['name']),
|
||||
)
|
||||
|
||||
def _on_plus_press(self, entry_name: str) -> None:
|
||||
entry = self._get_entry(entry_name)
|
||||
ba.internal.value_test(entry['name'], change=entry['increment'])
|
||||
# pylint: disable=consider-using-f-string
|
||||
ba.textwidget(
|
||||
edit=entry['widget'],
|
||||
text='%.4g' % ba.internal.value_test(entry['name']),
|
||||
)
|
||||
|
||||
def _do_back(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
backwin = (
|
||||
self._back_call()
|
||||
if self._back_call is not None
|
||||
else AdvancedSettingsWindow(transition='in_left')
|
||||
)
|
||||
ba.app.ui.set_main_menu_window(backwin.get_root_widget())
|
||||
283
dist/ba_data/python/bastd/ui/settings/touchscreen.py
vendored
Normal file
283
dist/ba_data/python/bastd/ui/settings/touchscreen.py
vendored
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI settings functionality related to touchscreens."""
|
||||
from __future__ import annotations
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
|
||||
class TouchscreenSettingsWindow(ba.Window):
|
||||
"""Settings window for touchscreens."""
|
||||
|
||||
def __del__(self) -> None:
|
||||
# Note - this happens in 'back' too;
|
||||
# we just do it here too in case the window is closed by other means.
|
||||
|
||||
# FIXME: Could switch to a UI destroy callback now that those are a
|
||||
# thing that exists.
|
||||
ba.internal.set_touchscreen_editing(False)
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
self._width = 650
|
||||
self._height = 380
|
||||
self._spacing = 40
|
||||
self._r = 'configTouchscreenWindow'
|
||||
|
||||
ba.internal.set_touchscreen_editing(True)
|
||||
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
transition='in_right',
|
||||
scale=(
|
||||
1.9
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.55
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.2
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(55, self._height - 60),
|
||||
size=(120, 60),
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
scale=0.8,
|
||||
on_activate_call=self._back,
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(25, self._height - 50),
|
||||
size=(self._width, 25),
|
||||
text=ba.Lstr(resource=self._r + '.titleText'),
|
||||
color=ba.app.ui.title_color,
|
||||
maxwidth=280,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
ba.buttonwidget(
|
||||
edit=btn,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
self._scroll_width = self._width - 100
|
||||
self._scroll_height = self._height - 110
|
||||
self._sub_width = self._scroll_width - 20
|
||||
self._sub_height = 360
|
||||
|
||||
self._scrollwidget = ba.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
(self._width - self._scroll_width) * 0.5,
|
||||
self._height - 65 - self._scroll_height,
|
||||
),
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
claims_left_right=True,
|
||||
claims_tab=True,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
self._subcontainer = ba.containerwidget(
|
||||
parent=self._scrollwidget,
|
||||
size=(self._sub_width, self._sub_height),
|
||||
background=False,
|
||||
claims_left_right=True,
|
||||
claims_tab=True,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
self._build_gui()
|
||||
|
||||
def _build_gui(self) -> None:
|
||||
from bastd.ui.config import ConfigNumberEdit, ConfigCheckBox
|
||||
from bastd.ui.radiogroup import make_radio_group
|
||||
|
||||
# Clear anything already there.
|
||||
children = self._subcontainer.get_children()
|
||||
for child in children:
|
||||
child.delete()
|
||||
h = 30
|
||||
v = self._sub_height - 85
|
||||
clr = (0.8, 0.8, 0.8, 1.0)
|
||||
clr2 = (0.8, 0.8, 0.8)
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(-10, v + 43),
|
||||
size=(self._sub_width, 25),
|
||||
text=ba.Lstr(resource=self._r + '.swipeInfoText'),
|
||||
flatness=1.0,
|
||||
color=(0, 0.9, 0.1, 0.7),
|
||||
maxwidth=self._sub_width * 0.9,
|
||||
scale=0.55,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
cur_val = ba.app.config.get('Touch Movement Control Type', 'swipe')
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h, v - 2),
|
||||
size=(0, 30),
|
||||
text=ba.Lstr(resource=self._r + '.movementText'),
|
||||
maxwidth=190,
|
||||
color=clr,
|
||||
v_align='center',
|
||||
)
|
||||
cb1 = ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h + 220, v),
|
||||
size=(170, 30),
|
||||
text=ba.Lstr(resource=self._r + '.joystickText'),
|
||||
maxwidth=100,
|
||||
textcolor=clr2,
|
||||
scale=0.9,
|
||||
)
|
||||
cb2 = ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h + 357, v),
|
||||
size=(170, 30),
|
||||
text=ba.Lstr(resource=self._r + '.swipeText'),
|
||||
maxwidth=100,
|
||||
textcolor=clr2,
|
||||
value=False,
|
||||
scale=0.9,
|
||||
)
|
||||
make_radio_group(
|
||||
(cb1, cb2), ('joystick', 'swipe'), cur_val, self._movement_changed
|
||||
)
|
||||
v -= 50
|
||||
ConfigNumberEdit(
|
||||
parent=self._subcontainer,
|
||||
position=(h, v),
|
||||
xoffset=65,
|
||||
configkey='Touch Controls Scale Movement',
|
||||
displayname=ba.Lstr(resource=self._r + '.movementControlScaleText'),
|
||||
changesound=False,
|
||||
minval=0.1,
|
||||
maxval=4.0,
|
||||
increment=0.1,
|
||||
)
|
||||
v -= 50
|
||||
cur_val = ba.app.config.get('Touch Action Control Type', 'buttons')
|
||||
ba.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h, v - 2),
|
||||
size=(0, 30),
|
||||
text=ba.Lstr(resource=self._r + '.actionsText'),
|
||||
maxwidth=190,
|
||||
color=clr,
|
||||
v_align='center',
|
||||
)
|
||||
cb1 = ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h + 220, v),
|
||||
size=(170, 30),
|
||||
text=ba.Lstr(resource=self._r + '.buttonsText'),
|
||||
maxwidth=100,
|
||||
textcolor=clr2,
|
||||
scale=0.9,
|
||||
)
|
||||
cb2 = ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(h + 357, v),
|
||||
size=(170, 30),
|
||||
text=ba.Lstr(resource=self._r + '.swipeText'),
|
||||
maxwidth=100,
|
||||
textcolor=clr2,
|
||||
scale=0.9,
|
||||
)
|
||||
make_radio_group(
|
||||
(cb1, cb2), ('buttons', 'swipe'), cur_val, self._actions_changed
|
||||
)
|
||||
v -= 50
|
||||
ConfigNumberEdit(
|
||||
parent=self._subcontainer,
|
||||
position=(h, v),
|
||||
xoffset=65,
|
||||
configkey='Touch Controls Scale Actions',
|
||||
displayname=ba.Lstr(resource=self._r + '.actionControlScaleText'),
|
||||
changesound=False,
|
||||
minval=0.1,
|
||||
maxval=4.0,
|
||||
increment=0.1,
|
||||
)
|
||||
|
||||
v -= 50
|
||||
ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
position=(h, v),
|
||||
size=(400, 30),
|
||||
maxwidth=400,
|
||||
configkey='Touch Controls Swipe Hidden',
|
||||
displayname=ba.Lstr(resource=self._r + '.swipeControlsHiddenText'),
|
||||
)
|
||||
v -= 65
|
||||
|
||||
ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5 - 70, v),
|
||||
size=(170, 60),
|
||||
label=ba.Lstr(resource=self._r + '.resetText'),
|
||||
scale=0.75,
|
||||
on_activate_call=self._reset,
|
||||
)
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, 38),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
text=ba.Lstr(resource=self._r + '.dragControlsText'),
|
||||
maxwidth=self._width * 0.8,
|
||||
scale=0.65,
|
||||
color=(1, 1, 1, 0.4),
|
||||
)
|
||||
|
||||
def _actions_changed(self, v: str) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg['Touch Action Control Type'] = v
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _movement_changed(self, v: str) -> None:
|
||||
cfg = ba.app.config
|
||||
cfg['Touch Movement Control Type'] = v
|
||||
cfg.apply_and_commit()
|
||||
|
||||
def _reset(self) -> None:
|
||||
cfg = ba.app.config
|
||||
cfgkeys = [
|
||||
'Touch Movement Control Type',
|
||||
'Touch Action Control Type',
|
||||
'Touch Controls Scale',
|
||||
'Touch Controls Scale Movement',
|
||||
'Touch Controls Scale Actions',
|
||||
'Touch Controls Swipe Hidden',
|
||||
'Touch DPad X',
|
||||
'Touch DPad Y',
|
||||
'Touch Buttons X',
|
||||
'Touch Buttons Y',
|
||||
]
|
||||
for cfgkey in cfgkeys:
|
||||
if cfgkey in cfg:
|
||||
del cfg[cfgkey]
|
||||
cfg.apply_and_commit()
|
||||
ba.timer(0, self._build_gui, timetype=ba.TimeType.REAL)
|
||||
|
||||
def _back(self) -> None:
|
||||
from bastd.ui.settings import controls
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
controls.ControlsSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
)
|
||||
ba.internal.set_touchscreen_editing(False)
|
||||
90
dist/ba_data/python/bastd/ui/settings/vrtesting.py
vendored
Normal file
90
dist/ba_data/python/bastd/ui/settings/vrtesting.py
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides UI for testing vr settings."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
from bastd.ui.settings.testing import TestingWindow
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class VRTestingWindow(TestingWindow):
|
||||
"""Window for testing vr settings."""
|
||||
|
||||
def __init__(self, transition: str = 'in_right'):
|
||||
|
||||
entries: list[dict[str, Any]] = []
|
||||
app = ba.app
|
||||
# these are gear-vr only
|
||||
if app.platform == 'android' and app.subplatform == 'oculus':
|
||||
entries += [
|
||||
{
|
||||
'name': 'timeWarpDebug',
|
||||
'label': 'Time Warp Debug',
|
||||
'increment': 1.0,
|
||||
},
|
||||
{
|
||||
'name': 'chromaticAberrationCorrection',
|
||||
'label': 'Chromatic Aberration Correction',
|
||||
'increment': 1.0,
|
||||
},
|
||||
{
|
||||
'name': 'vrMinimumVSyncs',
|
||||
'label': 'Minimum Vsyncs',
|
||||
'increment': 1.0,
|
||||
},
|
||||
# {'name':'eyeOffsX','label':'Eye IPD','increment':0.001}
|
||||
]
|
||||
# cardboard/gearvr get eye offset controls..
|
||||
# if app.platform == 'android':
|
||||
# entries += [
|
||||
# {'name':'eyeOffsY','label':'Eye Offset Y','increment':0.01},
|
||||
# {'name':'eyeOffsZ','label':'Eye Offset Z','increment':0.005}]
|
||||
# everyone gets head-scale
|
||||
entries += [
|
||||
{'name': 'headScale', 'label': 'Head Scale', 'increment': 1.0}
|
||||
]
|
||||
# and everyone gets all these..
|
||||
entries += [
|
||||
{
|
||||
'name': 'vrCamOffsetY',
|
||||
'label': 'In-Game Cam Offset Y',
|
||||
'increment': 0.1,
|
||||
},
|
||||
{
|
||||
'name': 'vrCamOffsetZ',
|
||||
'label': 'In-Game Cam Offset Z',
|
||||
'increment': 0.1,
|
||||
},
|
||||
{
|
||||
'name': 'vrOverlayScale',
|
||||
'label': 'Overlay Scale',
|
||||
'increment': 0.025,
|
||||
},
|
||||
{
|
||||
'name': 'allowCameraMovement',
|
||||
'label': 'Allow Camera Movement',
|
||||
'increment': 1.0,
|
||||
},
|
||||
{
|
||||
'name': 'cameraPanSpeedScale',
|
||||
'label': 'Camera Movement Speed',
|
||||
'increment': 0.1,
|
||||
},
|
||||
{
|
||||
'name': 'showOverlayBounds',
|
||||
'label': 'Show Overlay Bounds',
|
||||
'increment': 1,
|
||||
},
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
ba.Lstr(resource='settingsWindowAdvanced.vrTestingText'),
|
||||
entries,
|
||||
transition,
|
||||
)
|
||||
251
dist/ba_data/python/bastd/ui/settings/wiimote.py
vendored
Normal file
251
dist/ba_data/python/bastd/ui/settings/wiimote.py
vendored
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Settings UI functionality related to wiimote support."""
|
||||
from __future__ import annotations
|
||||
|
||||
import _ba
|
||||
import ba
|
||||
|
||||
|
||||
class WiimoteSettingsWindow(ba.Window):
|
||||
"""Window for setting up Wiimotes."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._r = 'wiimoteSetupWindow'
|
||||
width = 600
|
||||
height = 480
|
||||
spacing = 40
|
||||
super().__init__(root_widget=ba.containerwidget(size=(width, height),
|
||||
transition='in_right'))
|
||||
|
||||
btn = ba.buttonwidget(parent=self._root_widget,
|
||||
position=(55, height - 50),
|
||||
size=(120, 60),
|
||||
scale=0.8,
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
on_activate_call=self._back)
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(width * 0.5, height - 28),
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(resource=self._r + '.titleText'),
|
||||
maxwidth=270,
|
||||
color=ba.app.ui.title_color,
|
||||
h_align='center',
|
||||
v_align='center')
|
||||
|
||||
ba.buttonwidget(edit=btn,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK))
|
||||
|
||||
v = height - 60.0
|
||||
v -= spacing
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(width * 0.5, v - 80),
|
||||
size=(0, 0),
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
scale=0.75,
|
||||
text=ba.Lstr(resource=self._r + '.macInstructionsText'),
|
||||
maxwidth=width * 0.95,
|
||||
max_height=height * 0.5,
|
||||
h_align='center',
|
||||
v_align='center')
|
||||
v -= 230
|
||||
button_width = 200
|
||||
v -= 30
|
||||
btn = ba.buttonwidget(parent=self._root_widget,
|
||||
position=(width / 2 - button_width / 2, v + 1),
|
||||
autoselect=True,
|
||||
size=(button_width, 50),
|
||||
label=ba.Lstr(resource=self._r + '.listenText'),
|
||||
on_activate_call=WiimoteListenWindow)
|
||||
ba.containerwidget(edit=self._root_widget, start_button=btn)
|
||||
v -= spacing * 1.1
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(width * 0.5, v),
|
||||
size=(0, 0),
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
scale=0.8,
|
||||
maxwidth=width * 0.95,
|
||||
text=ba.Lstr(resource=self._r + '.thanksText'),
|
||||
h_align='center',
|
||||
v_align='center')
|
||||
v -= 30
|
||||
this_button_width = 200
|
||||
ba.buttonwidget(parent=self._root_widget,
|
||||
position=(width / 2 - this_button_width / 2, v - 14),
|
||||
color=(0.45, 0.4, 0.5),
|
||||
autoselect=True,
|
||||
size=(this_button_width, 15),
|
||||
label=ba.Lstr(resource=self._r + '.copyrightText'),
|
||||
textcolor=(0.55, 0.5, 0.6),
|
||||
text_scale=0.6,
|
||||
on_activate_call=WiimoteLicenseWindow)
|
||||
|
||||
def _back(self) -> None:
|
||||
from bastd.ui.settings import controls
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
controls.ControlsSettingsWindow(
|
||||
transition='in_left').get_root_widget())
|
||||
|
||||
|
||||
class WiimoteListenWindow(ba.Window):
|
||||
"""Window shown while listening for a wiimote connection."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._r = 'wiimoteListenWindow'
|
||||
width = 650
|
||||
height = 210
|
||||
super().__init__(root_widget=ba.containerwidget(size=(width, height),
|
||||
transition='in_right'))
|
||||
btn = ba.buttonwidget(parent=self._root_widget,
|
||||
position=(35, height - 60),
|
||||
size=(140, 60),
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='cancelText'),
|
||||
scale=0.8,
|
||||
on_activate_call=self._dismiss)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
_ba.start_listening_for_wii_remotes()
|
||||
self._wiimote_connect_counter = 15
|
||||
ba.app.ui.dismiss_wii_remotes_window_call = ba.WeakCall(self._dismiss)
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(15, height - 55),
|
||||
size=(width - 30, 30),
|
||||
text=ba.Lstr(resource=self._r + '.listeningText'),
|
||||
color=ba.app.ui.title_color,
|
||||
maxwidth=320,
|
||||
h_align='center',
|
||||
v_align='center')
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(15, height - 110),
|
||||
size=(width - 30, 30),
|
||||
scale=1.0,
|
||||
text=ba.Lstr(resource=self._r + '.pressText'),
|
||||
maxwidth=width * 0.9,
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
h_align='center',
|
||||
v_align='center')
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(15, height - 140),
|
||||
size=(width - 30, 30),
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
scale=0.55,
|
||||
text=ba.Lstr(resource=self._r + '.pressText2'),
|
||||
maxwidth=width * 0.95,
|
||||
h_align='center',
|
||||
v_align='center')
|
||||
self._counter_text = ba.textwidget(parent=self._root_widget,
|
||||
position=(15, 23),
|
||||
size=(width - 30, 30),
|
||||
scale=1.2,
|
||||
text='15',
|
||||
h_align='center',
|
||||
v_align='top')
|
||||
for i in range(1, 15):
|
||||
ba.timer(1.0 * i,
|
||||
ba.WeakCall(self._decrement),
|
||||
timetype=ba.TimeType.REAL)
|
||||
ba.timer(15.0, ba.WeakCall(self._dismiss), timetype=ba.TimeType.REAL)
|
||||
|
||||
def _decrement(self) -> None:
|
||||
self._wiimote_connect_counter -= 1
|
||||
ba.textwidget(edit=self._counter_text,
|
||||
text=str(self._wiimote_connect_counter))
|
||||
|
||||
def _dismiss(self) -> None:
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
_ba.stop_listening_for_wii_remotes()
|
||||
|
||||
|
||||
class WiimoteLicenseWindow(ba.Window):
|
||||
"""Window displaying the Darwiinremote software license."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._r = 'wiimoteLicenseWindow'
|
||||
width = 750
|
||||
height = 550
|
||||
super().__init__(root_widget=ba.containerwidget(size=(width, height),
|
||||
transition='in_right'))
|
||||
btn = ba.buttonwidget(parent=self._root_widget,
|
||||
position=(65, height - 50),
|
||||
size=(120, 60),
|
||||
scale=0.8,
|
||||
autoselect=True,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
on_activate_call=self._close)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(0, height - 48),
|
||||
size=(width, 30),
|
||||
text=ba.Lstr(resource=self._r + '.titleText'),
|
||||
h_align='center',
|
||||
color=ba.app.ui.title_color,
|
||||
v_align='center')
|
||||
license_text = (
|
||||
'Copyright (c) 2007, DarwiinRemote Team\n'
|
||||
'All rights reserved.\n'
|
||||
'\n'
|
||||
' Redistribution and use in source and binary forms, with or '
|
||||
'without modification,\n'
|
||||
' are permitted provided that'
|
||||
' the following conditions are met:\n'
|
||||
'\n'
|
||||
'1. Redistributions of source code must retain the above copyright'
|
||||
' notice, this\n'
|
||||
' list of conditions and the following disclaimer.\n'
|
||||
'2. Redistributions in binary form must reproduce the above'
|
||||
' copyright notice, this\n'
|
||||
' list of conditions and the following disclaimer in the'
|
||||
' documentation and/or other\n'
|
||||
' materials provided with the distribution.\n'
|
||||
'3. Neither the name of this project nor the names of its'
|
||||
' contributors may be used to\n'
|
||||
' endorse or promote products derived from this software'
|
||||
' without specific prior\n'
|
||||
' written permission.\n'
|
||||
'\n'
|
||||
'THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND'
|
||||
' CONTRIBUTORS "AS IS"\n'
|
||||
'AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT'
|
||||
' LIMITED TO, THE\n'
|
||||
'IMPLIED WARRANTIES OF MERCHANTABILITY'
|
||||
' AND FITNESS FOR A PARTICULAR'
|
||||
' PURPOSE\n'
|
||||
'ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR'
|
||||
' CONTRIBUTORS BE\n'
|
||||
'LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,'
|
||||
' OR\n'
|
||||
'CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT'
|
||||
' OF\n'
|
||||
' SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;'
|
||||
' OR BUSINESS\n'
|
||||
'INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,'
|
||||
' WHETHER IN\n'
|
||||
'CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR'
|
||||
' OTHERWISE)\n'
|
||||
'ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF'
|
||||
' ADVISED OF THE\n'
|
||||
'POSSIBILITY OF SUCH DAMAGE.\n')
|
||||
license_text_scale = 0.62
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
position=(100, height * 0.45),
|
||||
size=(0, 0),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
padding=4,
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
scale=license_text_scale,
|
||||
maxwidth=width * 0.9 - 100,
|
||||
max_height=height * 0.85,
|
||||
text=license_text)
|
||||
|
||||
def _close(self) -> None:
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
138
dist/ba_data/python/bastd/ui/settings/xbox360controller.py
vendored
Normal file
138
dist/ba_data/python/bastd/ui/settings/xbox360controller.py
vendored
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality related to using xbox360 controllers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class XBox360ControllerSettingsWindow(ba.Window):
|
||||
"""UI showing info about xbox 360 controllers."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._r = 'xbox360ControllersWindow'
|
||||
width = 700
|
||||
height = 300 if ba.internal.is_running_on_fire_tv() else 485
|
||||
spacing = 40
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(
|
||||
root_widget=ba.containerwidget(
|
||||
size=(width, height),
|
||||
transition='in_right',
|
||||
scale=(
|
||||
1.4
|
||||
if uiscale is ba.UIScale.SMALL
|
||||
else 1.4
|
||||
if uiscale is ba.UIScale.MEDIUM
|
||||
else 1.0
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(35, height - 65),
|
||||
size=(120, 60),
|
||||
scale=0.84,
|
||||
label=ba.Lstr(resource='backText'),
|
||||
button_type='back',
|
||||
autoselect=True,
|
||||
on_activate_call=self._back,
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, height - 42),
|
||||
size=(0, 0),
|
||||
scale=0.85,
|
||||
text=ba.Lstr(
|
||||
resource=self._r + '.titleText',
|
||||
subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
|
||||
),
|
||||
color=ba.app.ui.title_color,
|
||||
maxwidth=400,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
ba.buttonwidget(
|
||||
edit=btn,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=ba.charstr(ba.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
v = height - 70
|
||||
v -= spacing
|
||||
|
||||
if ba.internal.is_running_on_fire_tv():
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, height * 0.47),
|
||||
size=(0, 0),
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
maxwidth=width * 0.95,
|
||||
max_height=height * 0.75,
|
||||
scale=0.7,
|
||||
text=ba.Lstr(resource=self._r + '.ouyaInstructionsText'),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
else:
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, v - 1),
|
||||
size=(0, 0),
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
maxwidth=width * 0.95,
|
||||
max_height=height * 0.22,
|
||||
text=ba.Lstr(resource=self._r + '.macInstructionsText'),
|
||||
scale=0.7,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
v -= 90
|
||||
b_width = 300
|
||||
btn = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=((width - b_width) * 0.5, v - 10),
|
||||
size=(b_width, 50),
|
||||
label=ba.Lstr(resource=self._r + '.getDriverText'),
|
||||
autoselect=True,
|
||||
on_activate_call=ba.Call(
|
||||
ba.open_url,
|
||||
'https://github.com/360Controller/360Controller/releases',
|
||||
),
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget, start_button=btn)
|
||||
v -= 60
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width * 0.5, v - 85),
|
||||
size=(0, 0),
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
maxwidth=width * 0.95,
|
||||
max_height=height * 0.46,
|
||||
scale=0.7,
|
||||
text=ba.Lstr(resource=self._r + '.macInstructions2Text'),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
def _back(self) -> None:
|
||||
from bastd.ui.settings import controls
|
||||
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
controls.ControlsSettingsWindow(
|
||||
transition='in_left'
|
||||
).get_root_widget()
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue