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
catch problems where a base class changes or removes a method and child
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
players on calculating when they are gonna respawn. (Thanks 3alTemp!)
- 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(
devicename: str, unique_id: str, name: str
devicename: str,
unique_id: str,
name: str,
default: bool = False,
) -> Any:
"""Returns a mapped value for an input device.
@ -30,8 +33,9 @@ def get_input_device_mapped_value(
subplatform = app.classic.subplatform
appconfig = babase.app.config
# If there's an entry in our config for this controller, use it.
if 'Controllers' in appconfig:
# If there's an entry in our config for this controller and
# we're not looking for our default mappings, use it.
if 'Controllers' in appconfig and not default:
ccfgs = appconfig['Controllers']
if devicename in ccfgs:
mapping = None

View File

@ -575,15 +575,18 @@ class ClassicSubsystem(babase.AppSubsystem):
)
def get_input_device_mapped_value(
self, device: bascenev1.InputDevice, name: str
self,
device: bascenev1.InputDevice,
name: str,
default: bool = False,
) -> 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
where available.
"""
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(

View File

@ -1,5 +1,6 @@
# Released under the MIT License. See LICENSE for details.
#
# pylint: disable=too-many-lines
"""Settings UI functionality related to gamepads."""
from __future__ import annotations
@ -7,15 +8,18 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from bauiv1lib.popup import PopupMenuWindow
import bascenev1 as bs
import bauiv1 as bui
if TYPE_CHECKING:
from typing import Any, Callable
from bauiv1lib.popup import PopupWindow
class GamepadSettingsWindow(bui.Window):
"""Window for configuring a gamepad."""
# pylint: disable=too-many-public-methods
def __init__(
self,
@ -34,7 +38,6 @@ class GamepadSettingsWindow(bui.Window):
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.
@ -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.
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-locals
assert bui.app.classic is not None
@ -77,77 +149,6 @@ class GamepadSettingsWindow(bui.Window):
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
if self._is_secondary:
@ -367,22 +368,27 @@ class GamepadSettingsWindow(bui.Window):
scale=1.0,
)
self._advanced_button = bui.buttonwidget(
self._more_button = bui.buttonwidget(
parent=self._root_widget,
autoselect=True,
label=bui.Lstr(resource=self._r + '.advancedText'),
label='...',
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,
on_activate_call=self._do_more,
)
try:
if cancel_button is not None and save_button is not None:
bui.widget(edit=cancel_button, right_widget=save_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:
logging.exception('Error wiring up gamepad config window.')
@ -392,7 +398,7 @@ class GamepadSettingsWindow(bui.Window):
def get_advanced_button(self) -> bui.Widget:
"""(internal)"""
return self._advanced_button
return self._more_button
def get_is_secondary(self) -> bool:
"""(internal)"""
@ -801,6 +807,78 @@ class GamepadSettingsWindow(bui.Window):
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:
classic = bui.app.classic
assert classic is not None

View File

@ -6,11 +6,13 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from bauiv1lib.popup import PopupMenuWindow
import bauiv1 as bui
import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any
from bauiv1lib.popup import PopupWindow
class ConfigKeyboardWindow(bui.Window):
@ -46,16 +48,12 @@ class ConfigKeyboardWindow(bui.Window):
)
)
self._settings: dict[str, int] = {}
self._get_config_mapping()
self._rebuild_ui()
def _rebuild_ui(self) -> 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] = {}
def _get_config_mapping(self, default: bool = False) -> None:
for button in [
'buttonJump',
'buttonPunch',
@ -68,12 +66,20 @@ class ConfigKeyboardWindow(bui.Window):
'buttonLeft',
'buttonRight',
]:
self._settings[button] = (
bui.app.classic.get_input_device_mapped_value(
self._input, button
)
assert bui.app.classic is not None
self._settings[
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(
parent=self._root_widget,
autoselect=True,
@ -99,9 +105,6 @@ class ConfigKeyboardWindow(bui.Window):
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
bui.textwidget(
parent=self._root_widget,
@ -211,6 +214,24 @@ class ConfigKeyboardWindow(bui.Window):
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:
button_id = self._settings[button_name]
if button_id == -1:
@ -280,6 +301,65 @@ class ConfigKeyboardWindow(bui.Window):
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:
from bauiv1lib.settings.controls import ControlsSettingsWindow