Merge pull request #651 from 3alTemp/keybind_reset

Reset Mapping button in controllers config menu
This commit is contained in:
Eric Froemling 2024-03-13 17:46:47 -07:00 committed by GitHub
commit be7eb36ba5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 265 additions and 99 deletions

View File

@ -20,6 +20,7 @@
languages; I feel it helps keep logic more understandable and should help us languages; I feel it helps keep logic more understandable and should help us
catch problems where a base class changes or removes a method and child catch problems where a base class changes or removes a method and child
classes forget to adapt to the change. classes forget to adapt to the change.
- Added a reset button in the input mapping menu. (Thanks Temp!)
- Respawn icons now have dotted steps showing decimal progress to assist - Respawn icons now have dotted steps showing decimal progress to assist
players on calculating when they are gonna respawn. (Thanks 3alTemp!) players on calculating when they are gonna respawn. (Thanks 3alTemp!)
- Replays now have rewind/fast-forward buttons!! (Thanks Dliwk, vishal332008!) - Replays now have rewind/fast-forward buttons!! (Thanks Dliwk, vishal332008!)

View File

@ -13,7 +13,10 @@ if TYPE_CHECKING:
def get_input_device_mapped_value( def get_input_device_mapped_value(
devicename: str, unique_id: str, name: str devicename: str,
unique_id: str,
name: str,
default: bool = False,
) -> Any: ) -> Any:
"""Returns a mapped value for an input device. """Returns a mapped value for an input device.
@ -30,8 +33,9 @@ def get_input_device_mapped_value(
subplatform = app.classic.subplatform subplatform = app.classic.subplatform
appconfig = babase.app.config appconfig = babase.app.config
# If there's an entry in our config for this controller, use it. # If there's an entry in our config for this controller and
if 'Controllers' in appconfig: # we're not looking for our default mappings, use it.
if 'Controllers' in appconfig and not default:
ccfgs = appconfig['Controllers'] ccfgs = appconfig['Controllers']
if devicename in ccfgs: if devicename in ccfgs:
mapping = None mapping = None

View File

@ -575,15 +575,18 @@ class ClassicSubsystem(babase.AppSubsystem):
) )
def get_input_device_mapped_value( def get_input_device_mapped_value(
self, device: bascenev1.InputDevice, name: str self,
device: bascenev1.InputDevice,
name: str,
default: bool = False,
) -> Any: ) -> Any:
"""Returns a mapped value for an input device. """Return a mapped value for an input device.
This checks the user config and falls back to default values This checks the user config and falls back to default values
where available. where available.
""" """
return _input.get_input_device_mapped_value( return _input.get_input_device_mapped_value(
device.name, device.unique_identifier, name device.name, device.unique_identifier, name, default
) )
def get_input_device_map_hash( def get_input_device_map_hash(

View File

@ -1,5 +1,6 @@
# Released under the MIT License. See LICENSE for details. # Released under the MIT License. See LICENSE for details.
# #
# pylint: disable=too-many-lines
"""Settings UI functionality related to gamepads.""" """Settings UI functionality related to gamepads."""
from __future__ import annotations from __future__ import annotations
@ -7,15 +8,18 @@ from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from bauiv1lib.popup import PopupMenuWindow
import bascenev1 as bs import bascenev1 as bs
import bauiv1 as bui import bauiv1 as bui
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Callable from typing import Any, Callable
from bauiv1lib.popup import PopupWindow
class GamepadSettingsWindow(bui.Window): class GamepadSettingsWindow(bui.Window):
"""Window for configuring a gamepad.""" """Window for configuring a gamepad."""
# pylint: disable=too-many-public-methods
def __init__( def __init__(
self, self,
@ -34,7 +38,6 @@ class GamepadSettingsWindow(bui.Window):
self._name = self._input.name self._name = self._input.name
self._r = 'configGamepadWindow' self._r = 'configGamepadWindow'
self._settings = settings
self._transition_out = transition_out self._transition_out = transition_out
# We're a secondary gamepad if supplied with settings. # We're a secondary gamepad if supplied with settings.
@ -62,12 +65,81 @@ class GamepadSettingsWindow(bui.Window):
) )
) )
self._settings: dict[str, int] = {}
if not self._is_secondary:
self._get_config_mapping()
# Don't ask to config joysticks while we're in here. # Don't ask to config joysticks while we're in here.
self._rebuild_ui() self._rebuild_ui()
def _rebuild_ui(self) -> None: def _get_config_mapping(self, default: bool = False) -> None:
for button 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',
]:
assert bui.app.classic is not None
val = bui.app.classic.get_input_device_mapped_value(
self._input, button, default
)
if val != -1:
self._settings[button] = val
def _rebuild_ui(self, is_reset: bool = False) -> None:
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
# pylint: disable=too-many-locals
assert bui.app.classic is not None assert bui.app.classic is not None
@ -77,77 +149,6 @@ class GamepadSettingsWindow(bui.Window):
self._textwidgets: dict[str, bui.Widget] = {} self._textwidgets: dict[str, bui.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 = bui.app.classic.get_input_device_mapped_value(
self._input, skey
)
if val != -1:
self._settings[skey] = val
back_button: bui.Widget | None back_button: bui.Widget | None
if self._is_secondary: if self._is_secondary:
@ -367,22 +368,27 @@ class GamepadSettingsWindow(bui.Window):
scale=1.0, scale=1.0,
) )
self._advanced_button = bui.buttonwidget( self._more_button = bui.buttonwidget(
parent=self._root_widget, parent=self._root_widget,
autoselect=True, autoselect=True,
label=bui.Lstr(resource=self._r + '.advancedText'), label='...',
text_scale=0.9, text_scale=0.9,
color=(0.45, 0.4, 0.5), color=(0.45, 0.4, 0.5),
textcolor=(0.65, 0.6, 0.7), textcolor=(0.65, 0.6, 0.7),
position=(self._width - 300, 30), position=(self._width - 300, 30),
size=(130, 40), size=(130, 40),
on_activate_call=self._do_advanced, on_activate_call=self._do_more,
) )
try: try:
if cancel_button is not None and save_button is not None: if cancel_button is not None and save_button is not None:
bui.widget(edit=cancel_button, right_widget=save_button) bui.widget(edit=cancel_button, right_widget=save_button)
bui.widget(edit=save_button, left_widget=cancel_button) bui.widget(edit=save_button, left_widget=cancel_button)
if is_reset:
bui.containerwidget(
edit=self._root_widget,
selected_child=self._more_button,
)
except Exception: except Exception:
logging.exception('Error wiring up gamepad config window.') logging.exception('Error wiring up gamepad config window.')
@ -392,7 +398,7 @@ class GamepadSettingsWindow(bui.Window):
def get_advanced_button(self) -> bui.Widget: def get_advanced_button(self) -> bui.Widget:
"""(internal)""" """(internal)"""
return self._advanced_button return self._more_button
def get_is_secondary(self) -> bool: def get_is_secondary(self) -> bool:
"""(internal)""" """(internal)"""
@ -801,6 +807,78 @@ class GamepadSettingsWindow(bui.Window):
from_window=self._root_widget, from_window=self._root_widget,
) )
def _reset(self) -> None:
from bauiv1lib.confirm import ConfirmWindow
assert bui.app.classic is not None
ConfirmWindow(
# TODO: Implement a translation string for this!
'Are you sure you want to reset your button mapping?\n'
'This will also reset your advanced mappings\n'
'and secondary controller button mappings.',
self._do_reset,
width=490,
height=150,
)
def _do_reset(self) -> None:
"""Resets the input's mapping settings."""
from babase import InputDeviceNotFoundError
self._settings = {}
# Unplugging the controller while performing a
# mapping reset makes things go bonkers a little.
try:
self._get_config_mapping(default=True)
except InputDeviceNotFoundError:
pass
self._rebuild_ui(is_reset=True)
bui.getsound('gunCocking').play()
def _do_more(self) -> None:
"""Show a burger menu with extra settings."""
# pylint: disable=cyclic-import
choices: list[str] = [
'advanced',
'reset',
]
choices_display: list[bui.Lstr] = [
bui.Lstr(resource=self._r + '.advancedText'),
bui.Lstr(resource='settingsWindowAdvanced.resetText'),
]
uiscale = bui.app.ui_v1.uiscale
PopupMenuWindow(
position=self._more_button.get_screen_space_center(),
scale=(
2.3
if uiscale is bui.UIScale.SMALL
else 1.65
if uiscale is bui.UIScale.MEDIUM
else 1.23
),
width=150,
choices=choices,
choices_display=choices_display,
current_choice='advanced',
delegate=self,
)
def popup_menu_selected_choice(
self, popup_window: PopupMenuWindow, choice: str
) -> None:
"""Called when a choice is selected in the popup."""
del popup_window # unused
if choice == 'reset':
self._reset()
elif choice == 'advanced':
self._do_advanced()
else:
print(f'invalid choice: {choice}')
def popup_menu_closing(self, popup_window: PopupWindow) -> None:
"""Called when the popup is closing."""
def _save(self) -> None: def _save(self) -> None:
classic = bui.app.classic classic = bui.app.classic
assert classic is not None assert classic is not None

View File

@ -6,11 +6,13 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from bauiv1lib.popup import PopupMenuWindow
import bauiv1 as bui import bauiv1 as bui
import bascenev1 as bs import bascenev1 as bs
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any from typing import Any
from bauiv1lib.popup import PopupWindow
class ConfigKeyboardWindow(bui.Window): class ConfigKeyboardWindow(bui.Window):
@ -46,16 +48,12 @@ class ConfigKeyboardWindow(bui.Window):
) )
) )
self._settings: dict[str, int] = {}
self._get_config_mapping()
self._rebuild_ui() self._rebuild_ui()
def _rebuild_ui(self) -> None: def _get_config_mapping(self, default: bool = False) -> None:
assert bui.app.classic is not None
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 [ for button in [
'buttonJump', 'buttonJump',
'buttonPunch', 'buttonPunch',
@ -68,12 +66,20 @@ class ConfigKeyboardWindow(bui.Window):
'buttonLeft', 'buttonLeft',
'buttonRight', 'buttonRight',
]: ]:
self._settings[button] = ( assert bui.app.classic is not None
bui.app.classic.get_input_device_mapped_value( self._settings[
self._input, button button
) ] = bui.app.classic.get_input_device_mapped_value(
self._input, button, default
) )
def _rebuild_ui(self, is_reset: bool = False) -> None:
assert bui.app.classic is not None
for widget in self._root_widget.get_children():
widget.delete()
#b_off = 0 if self._unique_id != '#1' else 9
cancel_button = bui.buttonwidget( cancel_button = bui.buttonwidget(
parent=self._root_widget, parent=self._root_widget,
autoselect=True, autoselect=True,
@ -99,9 +105,6 @@ class ConfigKeyboardWindow(bui.Window):
start_button=save_button, start_button=save_button,
) )
bui.widget(edit=cancel_button, right_widget=save_button)
bui.widget(edit=save_button, left_widget=cancel_button)
v = self._height - 74.0 v = self._height - 74.0
bui.textwidget( bui.textwidget(
parent=self._root_widget, parent=self._root_widget,
@ -211,6 +214,24 @@ class ConfigKeyboardWindow(bui.Window):
scale=1.0, scale=1.0,
) )
self._more_button = bui.buttonwidget(
parent=self._root_widget,
autoselect=True,
label='...',
text_scale=0.9,
color=(0.45, 0.4, 0.5),
textcolor=(0.65, 0.6, 0.7),
position=(self._width * 0.5 - 65, 30),
size=(130, 40),
on_activate_call=self._do_more,
)
if is_reset:
bui.containerwidget(
edit=self._root_widget,
selected_child=self._more_button,
)
def _pretty_button_name(self, button_name: str) -> bui.Lstr: def _pretty_button_name(self, button_name: str) -> bui.Lstr:
button_id = self._settings[button_name] button_id = self._settings[button_name]
if button_id == -1: if button_id == -1:
@ -280,6 +301,65 @@ class ConfigKeyboardWindow(bui.Window):
from_window=self._root_widget, from_window=self._root_widget,
) )
def _reset(self) -> None:
from bauiv1lib.confirm import ConfirmWindow
assert bui.app.classic is not None
ConfirmWindow(
# TODO: Implement a translation string for this!
'Are you sure you want to reset your button mapping?',
self._do_reset,
width=480,
height=95,
)
def _do_reset(self) -> None:
"""Resets the input's mapping settings."""
self._settings = {}
self._get_config_mapping(default=True)
self._rebuild_ui(is_reset=True)
bui.getsound('gunCocking').play()
def _do_more(self) -> None:
"""Show a burger menu with extra settings."""
# pylint: disable=cyclic-import
choices: list[str] = [
'reset',
]
choices_display: list[bui.Lstr] = [
bui.Lstr(resource='settingsWindowAdvanced.resetText'),
]
uiscale = bui.app.ui_v1.uiscale
PopupMenuWindow(
position=self._more_button.get_screen_space_center(),
scale=(
2.3
if uiscale is bui.UIScale.SMALL
else 1.65
if uiscale is bui.UIScale.MEDIUM
else 1.23
),
width=150,
choices=choices,
choices_display=choices_display,
current_choice='reset',
delegate=self,
)
def popup_menu_selected_choice(
self, popup_window: PopupMenuWindow, choice: str
) -> None:
"""Called when a choice is selected in the popup."""
del popup_window # unused
if choice == 'reset':
self._reset()
else:
print(f'invalid choice: {choice}')
def popup_menu_closing(self, popup_window: PopupWindow) -> None:
"""Called when the popup is closing."""
def _save(self) -> None: def _save(self) -> None:
from bauiv1lib.settings.controls import ControlsSettingsWindow from bauiv1lib.settings.controls import ControlsSettingsWindow