mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2026-08-15 13:04:50 +00:00
973 lines
33 KiB
Python
973 lines
33 KiB
Python
# 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()
|