如何在 PyQt 中创建 joystick/controller 小部件?
How to create a joystick/controller widget in PyQt?
我想创建一个类似于此的操纵杆小部件
我当前的实现使用 QToolButton()
作为侧箭头,但我不确定如何在中间创建圆圈。当用户点击中间点并将其拖向箭头时,它应该记录移动。我正在考虑使用 paintEvent()
和 drawEclipse()
甚至 QDial()
但我不确定该怎么做。
from PyQt4 import QtCore, QtGui
import sys
class JoystickWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(JoystickWidget, self).__init__(parent)
self.field_joystick_up_button = QtGui.QToolButton()
self.field_joystick_up_button.setArrowType(QtCore.Qt.UpArrow)
self.field_joystick_up_button.clicked.connect(self.joystick_up)
self.field_joystick_up_button.setFixedWidth(75)
self.field_joystick_down_button = QtGui.QToolButton()
self.field_joystick_down_button.setArrowType(QtCore.Qt.DownArrow)
self.field_joystick_down_button.clicked.connect(self.joystick_down)
self.field_joystick_down_button.setFixedWidth(75)
self.field_joystick_right_button = QtGui.QToolButton()
self.field_joystick_right_button.setArrowType(QtCore.Qt.RightArrow)
self.field_joystick_right_button.clicked.connect(self.joystick_right)
self.field_joystick_right_button.setFixedWidth(75)
self.field_joystick_left_button = QtGui.QToolButton()
self.field_joystick_left_button.setArrowType(QtCore.Qt.LeftArrow)
self.field_joystick_left_button.clicked.connect(self.joystick_left)
self.field_joystick_left_button.setFixedWidth(75)
self.joystick_layout = QtGui.QVBoxLayout()
self.joystick_layout.addWidget(self.field_joystick_up_button,alignment=QtCore.Qt.AlignCenter)
self.joystick_layout_row = QtGui.QHBoxLayout()
self.joystick_layout_row.addWidget(self.field_joystick_left_button)
self.joystick_layout_row.addWidget(self.field_joystick_right_button)
self.joystick_layout.addLayout(self.joystick_layout_row)
self.joystick_layout.addWidget(self.field_joystick_down_button,alignment=QtCore.Qt.AlignCenter)
def get_joystick_layout(self):
return self.joystick_layout
def joystick_up(self):
print("Up")
def joystick_down(self):
print("Down")
def joystick_right(self):
print("Right")
def joystick_left(self):
print("Left")
if __name__ == '__main__':
# Create main application window
app = QtGui.QApplication([])
app.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
mw = QtGui.QMainWindow()
mw.setWindowTitle('Joystick example')
# Create and set widget layout
# Main widget container
cw = QtGui.QWidget()
ml = QtGui.QGridLayout()
cw.setLayout(ml)
mw.setCentralWidget(cw)
# Create joystick
joystick = JoystickWidget()
ml.addLayout(joystick.get_joystick_layout(),0,0)
mw.show()
## Start Qt event loop unless running in interactive mode or using pyside.
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
有时候,从头开始更容易。
如果您使用 QWidget::mousePressEvent()
、QWidget::mouseReleaseEvent()
、QWidget::mouseMoveEvent()
和 QWidget::paintEvent()
,您将能够控制操纵杆。
使用 QWidget::paintEvent()
在小部件的中心绘制操纵杆。
QWidget::mousePressEvent()
将在用户按下鼠标按钮时调用。您可以使用它开始移动您的操纵杆。
QWidget::mouseReleaseEvent()
在用户释放鼠标按钮时调用。用它来重置操纵杆。
QWidget::mouseMoveEvent()
称为鼠标在移动。用它来计算操纵杆的偏移量和方向(上、左、下或右)。如果你想要某种模拟操纵杆,你也可以使用中心和操纵杆之间的距离来获得一个介于 0(不动)和 1(最大)之间的数字。
例如:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
from enum import Enum
class Direction(Enum):
Left = 0
Right = 1
Up = 2
Down = 3
class Joystick(QWidget):
def __init__(self, parent=None):
super(Joystick, self).__init__(parent)
self.setMinimumSize(100, 100)
self.movingOffset = QPointF(0, 0)
self.grabCenter = False
self.__maxDistance = 50
def paintEvent(self, event):
painter = QPainter(self)
bounds = QRectF(-self.__maxDistance, -self.__maxDistance, self.__maxDistance * 2, self.__maxDistance * 2).translated(self._center())
painter.drawEllipse(bounds)
painter.setBrush(Qt.black)
painter.drawEllipse(self._centerEllipse())
def _centerEllipse(self):
if self.grabCenter:
return QRectF(-20, -20, 40, 40).translated(self.movingOffset)
return QRectF(-20, -20, 40, 40).translated(self._center())
def _center(self):
return QPointF(self.width()/2, self.height()/2)
def _boundJoystick(self, point):
limitLine = QLineF(self._center(), point)
if (limitLine.length() > self.__maxDistance):
limitLine.setLength(self.__maxDistance)
return limitLine.p2()
def joystickDirection(self):
if not self.grabCenter:
return 0
normVector = QLineF(self._center(), self.movingOffset)
currentDistance = normVector.length()
angle = normVector.angle()
distance = min(currentDistance / self.__maxDistance, 1.0)
if 45 <= angle < 135:
return (Direction.Up, distance)
elif 135 <= angle < 225:
return (Direction.Left, distance)
elif 225 <= angle < 315:
return (Direction.Down, distance)
return (Direction.Right, distance)
def mousePressEvent(self, ev):
self.grabCenter = self._centerEllipse().contains(ev.pos())
return super().mousePressEvent(ev)
def mouseReleaseEvent(self, event):
self.grabCenter = False
self.movingOffset = QPointF(0, 0)
self.update()
def mouseMoveEvent(self, event):
if self.grabCenter:
print("Moving")
self.movingOffset = self._boundJoystick(event.pos())
self.update()
print(self.joystickDirection())
if __name__ == '__main__':
# Create main application window
app = QApplication([])
app.setStyle(QStyleFactory.create("Cleanlooks"))
mw = QMainWindow()
mw.setWindowTitle('Joystick example')
# Create and set widget layout
# Main widget container
cw = QWidget()
ml = QGridLayout()
cw.setLayout(ml)
mw.setCentralWidget(cw)
# Create joystick
joystick = Joystick()
# ml.addLayout(joystick.get_joystick_layout(),0,0)
ml.addWidget(joystick,0,0)
mw.show()
## Start Qt event loop unless running in interactive mode or using pyside.
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QApplication.instance().exec_()
您可以使用相同的逻辑来创建按钮:为您的按钮定义四个区域并检查何时在这些区域按下鼠标。
我想创建一个类似于此的操纵杆小部件
我当前的实现使用 QToolButton()
作为侧箭头,但我不确定如何在中间创建圆圈。当用户点击中间点并将其拖向箭头时,它应该记录移动。我正在考虑使用 paintEvent()
和 drawEclipse()
甚至 QDial()
但我不确定该怎么做。
from PyQt4 import QtCore, QtGui
import sys
class JoystickWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(JoystickWidget, self).__init__(parent)
self.field_joystick_up_button = QtGui.QToolButton()
self.field_joystick_up_button.setArrowType(QtCore.Qt.UpArrow)
self.field_joystick_up_button.clicked.connect(self.joystick_up)
self.field_joystick_up_button.setFixedWidth(75)
self.field_joystick_down_button = QtGui.QToolButton()
self.field_joystick_down_button.setArrowType(QtCore.Qt.DownArrow)
self.field_joystick_down_button.clicked.connect(self.joystick_down)
self.field_joystick_down_button.setFixedWidth(75)
self.field_joystick_right_button = QtGui.QToolButton()
self.field_joystick_right_button.setArrowType(QtCore.Qt.RightArrow)
self.field_joystick_right_button.clicked.connect(self.joystick_right)
self.field_joystick_right_button.setFixedWidth(75)
self.field_joystick_left_button = QtGui.QToolButton()
self.field_joystick_left_button.setArrowType(QtCore.Qt.LeftArrow)
self.field_joystick_left_button.clicked.connect(self.joystick_left)
self.field_joystick_left_button.setFixedWidth(75)
self.joystick_layout = QtGui.QVBoxLayout()
self.joystick_layout.addWidget(self.field_joystick_up_button,alignment=QtCore.Qt.AlignCenter)
self.joystick_layout_row = QtGui.QHBoxLayout()
self.joystick_layout_row.addWidget(self.field_joystick_left_button)
self.joystick_layout_row.addWidget(self.field_joystick_right_button)
self.joystick_layout.addLayout(self.joystick_layout_row)
self.joystick_layout.addWidget(self.field_joystick_down_button,alignment=QtCore.Qt.AlignCenter)
def get_joystick_layout(self):
return self.joystick_layout
def joystick_up(self):
print("Up")
def joystick_down(self):
print("Down")
def joystick_right(self):
print("Right")
def joystick_left(self):
print("Left")
if __name__ == '__main__':
# Create main application window
app = QtGui.QApplication([])
app.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
mw = QtGui.QMainWindow()
mw.setWindowTitle('Joystick example')
# Create and set widget layout
# Main widget container
cw = QtGui.QWidget()
ml = QtGui.QGridLayout()
cw.setLayout(ml)
mw.setCentralWidget(cw)
# Create joystick
joystick = JoystickWidget()
ml.addLayout(joystick.get_joystick_layout(),0,0)
mw.show()
## Start Qt event loop unless running in interactive mode or using pyside.
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
有时候,从头开始更容易。
如果您使用 QWidget::mousePressEvent()
、QWidget::mouseReleaseEvent()
、QWidget::mouseMoveEvent()
和 QWidget::paintEvent()
,您将能够控制操纵杆。
使用 QWidget::paintEvent()
在小部件的中心绘制操纵杆。
QWidget::mousePressEvent()
将在用户按下鼠标按钮时调用。您可以使用它开始移动您的操纵杆。
QWidget::mouseReleaseEvent()
在用户释放鼠标按钮时调用。用它来重置操纵杆。
QWidget::mouseMoveEvent()
称为鼠标在移动。用它来计算操纵杆的偏移量和方向(上、左、下或右)。如果你想要某种模拟操纵杆,你也可以使用中心和操纵杆之间的距离来获得一个介于 0(不动)和 1(最大)之间的数字。
例如:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
from enum import Enum
class Direction(Enum):
Left = 0
Right = 1
Up = 2
Down = 3
class Joystick(QWidget):
def __init__(self, parent=None):
super(Joystick, self).__init__(parent)
self.setMinimumSize(100, 100)
self.movingOffset = QPointF(0, 0)
self.grabCenter = False
self.__maxDistance = 50
def paintEvent(self, event):
painter = QPainter(self)
bounds = QRectF(-self.__maxDistance, -self.__maxDistance, self.__maxDistance * 2, self.__maxDistance * 2).translated(self._center())
painter.drawEllipse(bounds)
painter.setBrush(Qt.black)
painter.drawEllipse(self._centerEllipse())
def _centerEllipse(self):
if self.grabCenter:
return QRectF(-20, -20, 40, 40).translated(self.movingOffset)
return QRectF(-20, -20, 40, 40).translated(self._center())
def _center(self):
return QPointF(self.width()/2, self.height()/2)
def _boundJoystick(self, point):
limitLine = QLineF(self._center(), point)
if (limitLine.length() > self.__maxDistance):
limitLine.setLength(self.__maxDistance)
return limitLine.p2()
def joystickDirection(self):
if not self.grabCenter:
return 0
normVector = QLineF(self._center(), self.movingOffset)
currentDistance = normVector.length()
angle = normVector.angle()
distance = min(currentDistance / self.__maxDistance, 1.0)
if 45 <= angle < 135:
return (Direction.Up, distance)
elif 135 <= angle < 225:
return (Direction.Left, distance)
elif 225 <= angle < 315:
return (Direction.Down, distance)
return (Direction.Right, distance)
def mousePressEvent(self, ev):
self.grabCenter = self._centerEllipse().contains(ev.pos())
return super().mousePressEvent(ev)
def mouseReleaseEvent(self, event):
self.grabCenter = False
self.movingOffset = QPointF(0, 0)
self.update()
def mouseMoveEvent(self, event):
if self.grabCenter:
print("Moving")
self.movingOffset = self._boundJoystick(event.pos())
self.update()
print(self.joystickDirection())
if __name__ == '__main__':
# Create main application window
app = QApplication([])
app.setStyle(QStyleFactory.create("Cleanlooks"))
mw = QMainWindow()
mw.setWindowTitle('Joystick example')
# Create and set widget layout
# Main widget container
cw = QWidget()
ml = QGridLayout()
cw.setLayout(ml)
mw.setCentralWidget(cw)
# Create joystick
joystick = Joystick()
# ml.addLayout(joystick.get_joystick_layout(),0,0)
ml.addWidget(joystick,0,0)
mw.show()
## Start Qt event loop unless running in interactive mode or using pyside.
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QApplication.instance().exec_()
您可以使用相同的逻辑来创建按钮:为您的按钮定义四个区域并检查何时在这些区域按下鼠标。