diff --git a/CMakeLists.txt b/CMakeLists.txt index 066ad792b..f724d1bfe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -193,6 +193,7 @@ set(antimicrox_SOURCES src/joysensor.cpp src/joysensorbuttonpushbutton.cpp src/joysensorpushbutton.cpp + src/joysensorstatusbox.cpp src/joystick.cpp src/keyboard/virtualkeyboardmousewidget.cpp src/keyboard/virtualkeypushbutton.cpp @@ -315,6 +316,7 @@ set(antimicrox_HEADERS src/joysensordirection.h src/joysensorbuttonpushbutton.h src/joysensorpushbutton.h + src/joysensorstatusbox.h src/joysensortype.h src/joystick.h src/keyboard/virtualkeyboardmousewidget.h diff --git a/src/joysensor.cpp b/src/joysensor.cpp index 1899161ac..5b6a1ce3a 100644 --- a/src/joysensor.cpp +++ b/src/joysensor.cpp @@ -153,6 +153,24 @@ JoySensorType JoySensor::getType() const { return m_type; } */ JoySensorDirection JoySensor::getCurrentDirection() const { return m_current_direction; } +/** + * @brief Get the assigned dead zone value. + * @return Assigned dead zone value in degree or degree/s. + */ +double JoySensor::getDeadZone() const { return m_dead_zone * 180 / M_PI; } + +/** + * @brief Get the assigned diagonal range value. + * @return Assigned diagonal range in degree or degree/s. + */ +double JoySensor::getDiagonalRange() const { return m_diagonal_range * 180 / M_PI; } + +/** + * @brief Get the assigned max zone value. + * @return Assigned max zone value in degree or degree/s. + */ +double JoySensor::getMaxZone() const { return m_max_zone * 180 / M_PI; } + /** * @brief Get the value for the corresponding X axis. * @return X axis value in m/s^2 for accelerometer or °/s for gyroscope. @@ -203,6 +221,10 @@ QString JoySensor::sensorTypeName() const bool JoySensor::inDeadZone(float *values) const { return false; } +double JoySensor::calculatePitch() const { return false; } + +double JoySensor::calculateRoll() const { return false; } + double JoySensor::calculateDirectionalDistance(JoySensorDirection direction) const { return 0; } /** diff --git a/src/joysensor.h b/src/joysensor.h index 859bd482b..848c5cb5f 100644 --- a/src/joysensor.h +++ b/src/joysensor.h @@ -53,12 +53,17 @@ class JoySensor : public QObject JoySensorType getType() const; JoySensorDirection getCurrentDirection() const; + double getDeadZone() const; + double getDiagonalRange() const; + double getMaxZone() const; float getXCoordinate() const; float getYCoordinate() const; float getZCoordinate() const; QString sensorTypeName() const; bool inDeadZone(float *values) const; + double calculatePitch() const; + double calculateRoll() const; double calculateDirectionalDistance(JoySensorDirection direction) const; QHash *getButtons(); @@ -84,6 +89,9 @@ class JoySensor : public QObject void populateButtons(); JoySensorType m_type; + double m_dead_zone; + double m_diagonal_range; + double m_max_zone; float m_current_value[3]; float m_pending_value[3]; diff --git a/src/joysensorstatusbox.cpp b/src/joysensorstatusbox.cpp new file mode 100644 index 000000000..73b807c44 --- /dev/null +++ b/src/joysensorstatusbox.cpp @@ -0,0 +1,254 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "joysensorstatusbox.h" + +#include "common.h" +#include "globalvariables.h" +#include "joyaxis.h" +#include "joysensor.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +JoySensorStatusBox::JoySensorStatusBox(QWidget *parent) + : QWidget(parent) + , m_sensor(nullptr) +{ +} + +/** + * @brief Sets the to be visualized sensor. + * + * It disconnects all events to the previous sensor if there was any, and + * connects them to the new sensor. + */ +void JoySensorStatusBox::setSensor(JoySensor *sensor) +{ + if (m_sensor != nullptr) + { + disconnect(m_sensor, SIGNAL(deadZoneChanged(double)), this, nullptr); + disconnect(m_sensor, SIGNAL(moved(float, float, float)), this, nullptr); + disconnect(m_sensor, SIGNAL(diagonalRangeChanged(double)), this, nullptr); + disconnect(m_sensor, SIGNAL(maxZoneChanged(double)), this, nullptr); + } + + m_sensor = sensor; + connect(m_sensor, SIGNAL(deadZoneChanged(double)), this, SLOT(update())); + connect(m_sensor, SIGNAL(moved(float, float, float)), this, SLOT(update())); + connect(m_sensor, SIGNAL(diagonalRangeChanged(double)), this, SLOT(update())); + connect(m_sensor, SIGNAL(maxZoneChanged(double)), this, SLOT(update())); + + update(); +} + +/** + * @brief Get the visualized sensor object + */ +JoySensor *JoySensorStatusBox::getSensor() const { return m_sensor; } + +QSize JoySensorStatusBox::sizeHint() const { return QSize(-1, -1); } + +void JoySensorStatusBox::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + PadderCommon::inputDaemonMutex.lock(); + drawArtificialHorizon(); + PadderCommon::inputDaemonMutex.unlock(); +} + +/** + * @brief draws the artificial horizon instrument on the screen + */ +void JoySensorStatusBox::drawArtificialHorizon() +{ + QPainter paint(this); + paint.setRenderHint(QPainter::Antialiasing, true); + + int side = qMin(width(), height()); + + QPen pen; + QPixmap pix(side, side); + pix.fill(Qt::transparent); + QPainter painter(&pix); + painter.setRenderHint(QPainter::Antialiasing, true); + + // Switch to centric coordinate system + painter.translate(side / 2.0, side / 2.0); + painter.scale(side * 0.45, -side * 0.45); + painter.save(); + + // Draw moving instrument parts + QPainterPath clippingPath; + clippingPath.addEllipse(QPointF(0, 0), 1, 1); + painter.setClipPath(clippingPath); + JoySensorType type; + + double pitch, roll, yaw; + if (m_sensor != nullptr) + { + type = m_sensor->getType(); + if (type == ACCELEROMETER) + { + pitch = -m_sensor->calculatePitch() * 180 / M_PI; + roll = m_sensor->calculateRoll() * 180 / M_PI; + yaw = 0; + } else + { + pitch = -m_sensor->getXCoordinate(); + roll = m_sensor->getYCoordinate(); + yaw = -m_sensor->getZCoordinate(); + } + } else + { + type = ACCELEROMETER; + pitch = 0; + roll = 0; + yaw = 0; + } + + pitch = qBound(-180.0, pitch, 180.0); + roll = qBound(-180.0, roll, 180.0); + yaw = qBound(-180.0, yaw, 180.0); + painter.translate(yaw / 90, pitch / 90); + painter.rotate(roll); + + pen.setColor(Qt::transparent); + painter.setPen(pen); + painter.setBrush(QBrush(QColor(64, 128, 255))); + painter.drawRect(QRectF(-10, 0, 20, 10)); + painter.setBrush(QBrush(Qt::black)); + painter.drawRect(QRectF(-10, -10, 20, 10)); + + // Draw dead zone + pen.setColor(Qt::red); + pen.setWidthF(0.02); + painter.setPen(pen); + painter.setBrush(QBrush(QColor(255, 0, 0, 128))); + double deadZone = m_sensor != nullptr ? m_sensor->getDeadZone() : 0.0; + painter.drawEllipse(QPointF(0, 0), deadZone / 90, deadZone / 90); + + // Draw max zone + QPainterPath maxZonePath; + double maxZone = m_sensor != nullptr ? m_sensor->getMaxZone() : 0.0; + maxZonePath.addEllipse(QPointF(0, 0), 10, 10); + maxZonePath.addEllipse(QPointF(0, 0), maxZone / 90, maxZone / 90); + pen.setColor(Qt::darkGreen); + pen.setWidthF(0.02); + painter.setPen(pen); + painter.setBrush(QBrush(QColor(0, 128, 0, 128))); + painter.drawPath(maxZonePath); + + // Draw diagonal zones + pen.setColor(Qt::green); + painter.setPen(pen); + painter.setBrush(QBrush(QColor(0, 255, 0, 128))); + + double diagonalRange = m_sensor != nullptr ? m_sensor->getDiagonalRange() : 0.0; + if (type == GYROSCOPE) + { + for (int i = 0; i < 4; ++i) + { + painter.drawPie(QRectF(-maxZone / 90, -maxZone / 90, 2 * maxZone / 90, 2 * maxZone / 90), + (45 + 90 * i - diagonalRange / 2) * 16, diagonalRange * 16); + } + } else + { + painter.drawPie(QRectF(-maxZone / 90, -maxZone / 90, 2 * maxZone / 90, 2 * maxZone / 90), + (135 - diagonalRange / 2) * 16, (diagonalRange + 90) * 16); + painter.drawPie(QRectF(-maxZone / 90, -maxZone / 90, 2 * maxZone / 90, 2 * maxZone / 90), + (-45 - diagonalRange / 2) * 16, (diagonalRange + 90) * 16); + } + + // Pitch scale: 30deg per line + pen.setColor(Qt::white); + pen.setWidthF(0.025); + painter.setPen(pen); + painter.setBrush(QBrush(Qt::transparent)); + for (int j = -180; j <= 180; j += 30) + { + painter.drawLine(QPointF(-10, j / 90.0), QPointF(10, j / 90.0)); + } + + // Yaw scale: 30deg per line + if (type == GYROSCOPE) + { + pen.setColor(Qt::white); + pen.setWidthF(0.025); + painter.setPen(pen); + painter.setBrush(QBrush(Qt::transparent)); + for (int j = -180; j <= 180; j += 30) + { + painter.drawLine(QPointF(j / 90.0, -10), QPointF(j / 90.0, 10)); + } + } + + // Draw fixed instrument parts + painter.restore(); + painter.save(); + pen.setColor(QColor(80, 80, 80)); + pen.setWidthF(0.2); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + painter.drawEllipse(QPointF(0, 0), 1, 1); + + // Draw scale + pen.setWidthF(0.05); + pen.setColor(Qt::yellow); + painter.setPen(pen); + + painter.drawLine(QPointF(0.3, 0), QPointF(0.2, 0)); + painter.drawLine(QPointF(-0.3, 0), QPointF(-0.2, 0)); + painter.drawArc(QRectF(-0.2, -0.2, 0.4, 0.4), 0 * 16, 180 * 16); + painter.drawPoint(QPointF(0, 0)); + + pen.setColor(Qt::white); + painter.setPen(pen); + for (int j = 0; j < 19; ++j) + { + painter.drawLine(QPointF(1, 0), QPointF(0.9, 0)); + painter.rotate(10.0); + } + + // Draw dead zone + painter.restore(); + pen.setColor(Qt::red); + pen.setWidthF(0.1); + painter.setPen(pen); + painter.setOpacity(0.5); + painter.drawArc(QRectF(-1, -1, 2, 2), -16 * deadZone, 16 * deadZone * 2); + painter.drawArc(QRectF(-1, -1, 2, 2), 16 * (180 - deadZone), 16 * deadZone * 2); + + // Draw max zone + pen.setColor(Qt::darkGreen); + painter.setPen(pen); + double tmpMaxZone = std::min(maxZone, 90.0); + painter.drawArc(QRectF(-1, -1, 2, 2), 16 * (90 - (90 - tmpMaxZone)), 16 * (90 - tmpMaxZone) * 2); + painter.drawArc(QRectF(-1, -1, 2, 2), 16 * (270 - (90 - tmpMaxZone)), 16 * (90 - tmpMaxZone) * 2); + + // Draw to window + paint.setCompositionMode(QPainter::CompositionMode_SourceOver); + paint.drawPixmap(pix.rect(), pix); +} diff --git a/src/joysensorstatusbox.h b/src/joysensorstatusbox.h new file mode 100644 index 000000000..7502d07f4 --- /dev/null +++ b/src/joysensorstatusbox.h @@ -0,0 +1,50 @@ +/* antimicrox Gamepad to KB+M event mapper + * Copyright (C) 2022 Max Maisel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include + +class JoySensor; +class QPaintEvent; + +/** + * @brief The GUI sensor position indicator widget + * + * It shows the yaw and roll angles on an artificial horizon instrument. + * In case of a gyroscope, the inner parts has an additional horizontal + * degree of freedom. + */ +class JoySensorStatusBox : public QWidget +{ + Q_OBJECT + + public: + explicit JoySensorStatusBox(QWidget *parent = nullptr); + + void setSensor(JoySensor *sensor); + JoySensor *getSensor() const; + + virtual QSize sizeHint() const override; + + protected: + virtual void paintEvent(QPaintEvent *event) override; + void drawArtificialHorizon(); + + private: + JoySensor *m_sensor; +};