vh-bombsquad-modded-server-.../dist/ba_data/python/bastd/ui/settings/gamepad.py
2024-02-19 18:29:19 +05:30

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()