Source code for pyvisor.GUI.tab_buttons.assign_button_box
import pygame
from PyQt5.QtGui import QPixmap, QCloseEvent
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QPushButton, QMessageBox, QWidget, QInputDialog
from ..model.behaviour import Behaviour
from ..model.gui_data_interface import GUIDataInterface
from ..model.scorer_action import ScorerAction
[docs]
class AssignButtonBox(QWidget):
"""Widget for a single button-to-action assignment.
Shows the action name, current binding, and an "assign button"
control. For gamepads, opens a dialog that polls for input;
for keyboards, opens a text entry dialog.
"""
def __init__(self,
parent_widget: QWidget,
gui_data_interface: GUIDataInterface,
action: ScorerAction,
color: str,
is_behaviour: bool):
super().__init__(parent_widget)
self.parent_widget = parent_widget
self.gui_data_interface = gui_data_interface
self.action = action
self.color = color
self._init_callbacks(gui_data_interface)
self.is_behaviour = is_behaviour
self._init_UI()
def _init_callbacks(self, gui_data_interface):
self._callback_id_binding = gui_data_interface.register_callback_key_binding_changed(
self.button_assignment_changed)
self._callback_id_color = gui_data_interface.callbacks_behaviour_color_changed.register(
self.set_color
)
self._callback_id_icon = gui_data_interface.callbacks_update_icon.register(
self._set_icon
)
self._callback_id_name = gui_data_interface.callbacks_behaviour_name_changed.register(
self._set_name
)
self._callback_id_removed = gui_data_interface.callbacks_behaviour_removed.register(
self.remove
)
def _set_name(self, action: ScorerAction):
if action is not self.action:
return
self.behav_label.setText(action.name)
[docs]
def set_color(self, action: ScorerAction):
if action is not self.action:
return
assert self.is_behaviour
self.color = action.color
self.behav_label.setStyleSheet('color: ' + self.color)
[docs]
def closeEvent(self, a0: QCloseEvent) -> None:
self.gui_data_interface.callbacks_key_binding_changed.pop(self._callback_id_binding)
self.gui_data_interface.callbacks_update_icon.pop(self._callback_id_icon)
self.gui_data_interface.callbacks_behaviour_color_changed.pop(self._callback_id_color)
self.gui_data_interface.callbacks_behaviour_name_changed.pop(self._callback_id_name)
self.gui_data_interface.callbacks_behaviour_removed.pop(self._callback_id_removed)
def _init_UI(self):
self.box = QHBoxLayout()
self.behav_label = QLabel(self.action.name)
self.behav_label.setStyleSheet('color: ' + self.color)
btn_set_uic = QPushButton('assign button')
btn_set_uic.clicked.connect(self.assign_button)
self._create_button_label()
if self.is_behaviour:
self._create_icon()
self.box.addWidget(self.imageLabel)
self.box.addWidget(self.behav_label)
self.box.addWidget(btn_set_uic)
self.box.addWidget(self.button_label)
self.setLayout(self.box)
def _set_icon(self, behaviour: Behaviour):
if behaviour is not self.action:
return
pixmap = QPixmap(self.action.icon_path)
pixmap = pixmap.scaledToWidth(20)
self.imageLabel.setPixmap(pixmap)
def _create_icon(self):
self.imageLabel = QLabel()
if self.action.icon_path is None:
return
pixmap = QPixmap(self.action.icon_path)
pixmap = pixmap.scaledToWidth(20)
self.imageLabel.setStyleSheet('color: ' + self.color)
self.imageLabel.setPixmap(pixmap)
def _create_button_label(self):
key = self.action.key_bindings[self.gui_data_interface.selected_device]
label = 'no button\nassigned' if key is None else key
self.button_label = QLabel(self)
if key is None:
self.button_label.setStyleSheet('color: #C0C0C0')
else:
self.button_label.setStyleSheet('color: #ffffff')
self.button_label.setText(label)
[docs]
def assign_button(self):
if self.gui_data_interface.selected_device is None:
QMessageBox.warning(self.parent_widget, 'Set device first!',
"You need to choose an input device first",
QMessageBox.Ok)
return
if self.gui_data_interface.selected_device == "Keyboard":
text, ok = QInputDialog.getText(self.parent(), 'Press', 'Key Entry:')
if not ok:
return
button_identifier = str(text)
else:
button_identifier = self.waitOnUICpress()
if button_identifier is None:
return
assigned_action, is_behaviour = self.gui_data_interface.get_action_assigned_to(
button_identifier)
# Clear this button from ALL old assignments (behaviours + movie actions)
self.gui_data_interface.steal_button(button_identifier)
# Assign to the new action
self.gui_data_interface.change_button_binding(self.action, button_identifier, self.is_behaviour)
[docs]
def button_assignment_changed(self, action: ScorerAction, is_behaviour: bool):
if action is not self.action:
return
binding = action.key_bindings[self.gui_data_interface.selected_device]
if binding is None:
self.button_label.setText("no button\nassigned")
self.button_label.setStyleSheet('color: #C0C0C0')
else:
self.button_label.setText(binding)
self.button_label.setStyleSheet('color: #ffffff')
[docs]
@staticmethod
def waitOnUICpress():
"""Wait for a gamepad button/axis/hat press with a visible dialog."""
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel as _QLabel
from PyQt5.QtCore import QTimer, Qt as _Qt
dlg = QDialog()
dlg.setWindowTitle("Waiting for gamepad input…")
dlg.setMinimumSize(320, 100)
layout = QVBoxLayout(dlg)
lbl = _QLabel("Press a button, trigger, or move a stick\n"
"on your gamepad to assign it.\n\n"
"Close this window to cancel.")
lbl.setAlignment(_Qt.AlignCenter)
layout.addWidget(lbl)
dlg.setLayout(layout)
result = [None]
def poll():
for event in pygame.event.get():
if event.type == pygame.JOYBUTTONDOWN:
result[0] = 'B' + str(event.button)
dlg.accept()
return
if event.type == pygame.JOYAXISMOTION:
value = event.dict['value']
axis = event.dict['axis']
if abs(value) > 0.75:
code = 'A' + str(axis)
code += '+' if value > 0 else '-'
result[0] = code
dlg.accept()
return
if event.type == pygame.JOYHATMOTION:
value = event.dict['value']
code = 'H' + str(value[0]) + str(value[1])
if code != 'H00':
result[0] = code
dlg.accept()
return
timer = QTimer(dlg)
timer.timeout.connect(poll)
timer.start(50) # poll every 50 ms
pygame.event.clear()
dlg.exec_()
timer.stop()
return result[0]