Fix doc path
[opentx.git] / companion / src / simulation / widgets / virtualjoystickwidget.cpp
blob8e9a4fa4559716ebdc8bf74833f5cacb032c321e
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #define GBALL_SIZE 25
22 #define GBALL_SIZE_MN 20
23 #define GBALL_SIZE_MX 35
24 #define CENTER_INTERVAL 50 // ms
26 #include "virtualjoystickwidget.h"
28 #include "boards.h"
29 #include "modeledit/node.h"
30 #include "helpers.h"
31 #include "radiotrimwidget.h"
32 #include "simulator.h"
34 VirtualJoystickWidget::VirtualJoystickWidget(QWidget *parent, QChar side, bool showTrims, bool showBtns, bool showValues, QSize size) :
35 QWidget(parent),
36 stickSide(side),
37 prefSize(size),
38 hTrimWidget(NULL),
39 vTrimWidget(NULL),
40 btnHoldX(NULL),
41 btnHoldY(NULL),
42 btnFixX(NULL),
43 btnFixY(NULL),
44 nodeLabelX(NULL),
45 nodeLabelY(NULL),
46 m_stickScale(1024),
47 m_stickPressed(false)
49 ar = (float)size.width() / size.height();
50 extraSize = QSize(0, 0);
52 // 5 col x 4 rows
53 layout = new QGridLayout(this);
54 layout->setContentsMargins(0, 0, 0, 0);
55 layout->setSpacing(0);
57 QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
58 setSizePolicy(sizePolicy);
60 gv = new QGraphicsView(this);
61 gv->setSizePolicy(sizePolicy);
62 gv->setMinimumSize(size);
63 gv->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
64 gv->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
65 gv->setRenderHints(QPainter::Antialiasing);
67 scene = new CustomGraphicsScene(gv);
68 scene->setItemIndexMethod(QGraphicsScene::NoIndex);
69 gv->setScene(scene);
71 node = new Node();
72 node->setPos(-GBALL_SIZE / 2, -GBALL_SIZE / 2);
73 node->setBallSize(GBALL_SIZE);
74 scene->addItem(node);
76 int colvt, colbb, colvx, colvy;
77 if (stickSide == 'L') {
78 colvt = 3;
79 colbb = 1;
80 colvx = 1;
81 colvy = 3;
83 else {
84 colvt = 1;
85 colbb = 3;
86 colvx = 3;
87 colvy = 1;
90 if (showTrims) {
91 hTrimWidget = createTrimWidget('H');
92 vTrimWidget = createTrimWidget('V');
94 layout->addWidget(vTrimWidget, 1, colvt, 1, 1);
95 layout->addWidget(hTrimWidget, 2, 2, 1, 1);
97 extraSize += QSize(vTrimWidget->sizeHint().width(), hTrimWidget->sizeHint().height());
99 else {
100 colvx = colvy = colvt;
103 if (showBtns) {
104 QVBoxLayout * btnbox = new QVBoxLayout();
105 btnbox->setContentsMargins(9, 9, 9, 9);
106 btnbox->setSpacing(2);
107 btnbox->addWidget(btnHoldY = createButtonWidget(HOLD_Y));
108 btnbox->addWidget(btnFixY = createButtonWidget(FIX_Y));
109 btnbox->addWidget(btnFixX = createButtonWidget(FIX_X));
110 btnbox->addWidget(btnHoldX = createButtonWidget(HOLD_X));
112 layout->addLayout(btnbox, 1, colbb, 1, 1);
114 extraSize.setWidth(extraSize.width() + btnbox->sizeHint().width());
117 if (showValues) {
118 QLayout * valX = createNodeValueLayout('X', nodeLabelX);
119 QLayout * valY = createNodeValueLayout('Y', nodeLabelY);
120 if (!showTrims) {
121 QVBoxLayout * vertXY = new QVBoxLayout();
122 vertXY->addLayout(valX);
123 vertXY->addLayout(valY);
124 layout->addLayout(vertXY, 1, colvx, 1, 1);
125 extraSize += QSize(nodeLabelX->sizeHint().width(), 0);
127 else {
128 layout->addLayout(valX, 2, colvx, 1, 1);
129 layout->addLayout(valY, 2, colvy, 1, 1);
133 layout->addItem(new QSpacerItem(0, 0), 0, 0, 1, 5); // r0 c0-4: top v spacer
134 layout->addItem(new QSpacerItem(0, 0), 1, 0, 2, 1); // r1-2 c0: left h spacer
135 layout->addWidget(gv, 1, 2, 1, 1); // r1 c2: stick widget
136 layout->addItem(new QSpacerItem(0, 0), 1, 4, 2, 1); // r1-2 c4: right h spacer
137 layout->addItem(new QSpacerItem(0, 0), 3, 0, 1, 5); // r4 c0-4: bot v spacer
139 setStickIndices(getStickIndex('H'), getStickIndex('V'));
141 connect(node, &Node::xChanged, this, &VirtualJoystickWidget::onNodeXChanged);
142 connect(node, &Node::yChanged, this, &VirtualJoystickWidget::onNodeYChanged);
144 connect(scene, &CustomGraphicsScene::mouseEvent, this, &VirtualJoystickWidget::onGsMouseEvent);
146 setSize(prefSize, frameSize());
148 centerStickTimer.setInterval(CENTER_INTERVAL);
149 connect(&centerStickTimer, &QTimer::timeout, this, &VirtualJoystickWidget::centerStick);
150 centerStickTimer.start();
153 void VirtualJoystickWidget::setStickX(qreal x)
155 node->setX(x);
158 void VirtualJoystickWidget::setStickY(qreal y)
160 node->setY(y);
163 void VirtualJoystickWidget::setStickPos(QPointF xy)
165 node->setPos(xy);
168 void VirtualJoystickWidget::setStickAxisValue(int index, int value)
170 using namespace Board;
171 qreal rvalue = value / 1024.0f;
173 switch (index) {
174 case STICK_AXIS_LH :
175 if (stickSide == 'L')
176 setStickX(rvalue);
177 break;
178 case STICK_AXIS_RH :
179 if (stickSide == 'R')
180 setStickX(rvalue);
181 break;
182 case STICK_AXIS_LV :
183 if (stickSide == 'L')
184 setStickY(rvalue);
185 break;
186 case STICK_AXIS_RV :
187 if (stickSide == 'R')
188 setStickY(rvalue);
189 break;
193 void VirtualJoystickWidget::centerStick()
195 node->stepToCenter();
198 qreal VirtualJoystickWidget::getStickX()
200 return getStickPos().x();
203 qreal VirtualJoystickWidget::getStickY()
205 return getStickPos().y();
208 QPointF VirtualJoystickWidget::getStickPos()
210 return QPointF(node->getX(), node->getY());
213 void VirtualJoystickWidget::setTrimsRange(int min, int max)
215 if (hTrimWidget)
216 hTrimWidget->setTrimRange(min, max);
217 if (vTrimWidget)
218 vTrimWidget->setTrimRange(min, max);
221 void VirtualJoystickWidget::setTrimsRange(int rng)
223 setTrimsRange(-rng, rng);
226 void VirtualJoystickWidget::setTrimRange(int index, int min, int max)
228 if (index == Board::TRIM_AXIS_COUNT) {
229 setTrimsRange(min, max);
230 return;
232 RadioTrimWidget * trim = getTrimWidget(index);
233 if (trim) {
234 trim->setTrimRange(min, max);
238 void VirtualJoystickWidget::setTrimValue(int index, int value)
240 RadioTrimWidget * trim = getTrimWidget(index);
241 if (trim) {
242 trim->setValue(value);
246 int VirtualJoystickWidget::getStickScale() const
248 return m_stickScale;
251 void VirtualJoystickWidget::setStickScale(int stickScale)
253 m_stickScale = stickScale;
256 void VirtualJoystickWidget::setWidgetValue(const RadioWidget::RadioWidgetType type, const int index, const int value)
258 if (type == RadioWidget::RADIO_WIDGET_TRIM)
259 setTrimValue(index, value);
260 else if (type == RadioWidget::RADIO_WIDGET_STICK)
261 setStickAxisValue(index, value);
264 int VirtualJoystickWidget::getTrimValue(int which)
266 RadioTrimWidget * trim = getTrimWidget(which);
267 if (trim) {
268 return trim->getValue();
270 return 0;
273 void VirtualJoystickWidget::setStickIndices(int xIdx, int yIdx)
275 m_xIndex = xIdx;
276 m_yIndex = yIdx;
279 void VirtualJoystickWidget::setStickConstraint(quint8 index, bool active)
281 if (btnHoldX == NULL || !index)
282 return; // no buttons
284 if (index & HOLD_X)
285 btnHoldX->setChecked(active);
286 if (index & HOLD_Y)
287 btnHoldY->setChecked(active);
288 if (index & FIX_X)
289 btnFixX->setChecked(active);
290 if (index & FIX_Y)
291 btnFixY->setChecked(active);
294 void VirtualJoystickWidget::setStickColor(const QColor & color)
296 node->setColor(color);
299 void VirtualJoystickWidget::loadDefaultsForMode(const unsigned mode)
301 if (((mode & 1) && stickSide == 'L') || (!(mode & 1) && stickSide == 'R')) {
302 setStickConstraint(HOLD_Y, true);
303 setStickY(1.0);
304 onNodeYChanged();
308 QSize VirtualJoystickWidget::sizeHint() const {
309 return prefSize;
312 void VirtualJoystickWidget::resizeEvent(QResizeEvent * event)
314 QWidget::resizeEvent(event);
315 setSize(event->size(), event->oldSize());
318 void VirtualJoystickWidget::onNodeXChanged()
320 emit valueChange(RadioWidget::RADIO_WIDGET_STICK, m_xIndex, int(m_stickScale * getStickX()));
321 updateNodeValueLabels();
324 void VirtualJoystickWidget::onNodeYChanged()
326 emit valueChange(RadioWidget::RADIO_WIDGET_STICK, m_yIndex, int(-m_stickScale * getStickY()));
327 updateNodeValueLabels();
330 void VirtualJoystickWidget::setSize(const QSize & size, const QSize &)
332 float thisAspectRatio = (float)size.width() / size.height();
333 float newGvSz, spacerSz;
334 bool sized = false;
336 if (thisAspectRatio > ar) {
337 // constrain width
338 newGvSz = (height() - extraSize.height()) * ar; // new width
339 spacerSz = (width() - newGvSz - extraSize.width()) / 2; // + 0.5;
340 if (spacerSz >= 0.0f) {
341 layout->setRowStretch(0, 0);
342 layout->setColumnStretch(0, spacerSz);
343 layout->setColumnStretch(4, spacerSz);
344 layout->setRowStretch(3, 0);
345 sized = true;
348 if (!sized) {
349 // constrain height
350 newGvSz = (width() - extraSize.width()) * ar; // new height
351 spacerSz = (height() - newGvSz - extraSize.height()) / 2; // + 0.5;
352 spacerSz = qMax(spacerSz, 0.0f);
353 layout->setRowStretch(0, spacerSz);
354 layout->setColumnStretch(0, 0);
355 layout->setColumnStretch(4, 0);
356 layout->setRowStretch(3, spacerSz);
359 layout->setColumnStretch(2, newGvSz);
360 layout->setRowStretch(1, newGvSz);
362 gv->resize(newGvSz, newGvSz);
363 gv->updateGeometry();
365 int ballSize = (newGvSz * GBALL_SIZE * 0.005f);
366 ballSize = qMin(GBALL_SIZE_MX, qMax(ballSize, GBALL_SIZE_MN));
368 QRectF qr = (QRectF)gv->contentsRect();
369 qreal w = qr.width() - ballSize;
370 qreal h = qr.height() - ballSize;
371 qreal cx = qr.width() / 2;
372 qreal cy = qr.height() / 2;
373 qreal nodeX = node->getX();
374 qreal nodeY = node->getY();
376 scene->setSceneRect(-cx,-cy,w,h);
378 node->setBallSize(ballSize);
379 node->setX(nodeX);
380 node->setY(nodeY);
382 //qDebug() << thisAspectRatio << size << newGvSz << spacerSz << extraSize << gv->geometry() << gv->contentsRect() << gv->frameRect() << getStickPos();
385 RadioTrimWidget * VirtualJoystickWidget::createTrimWidget(QChar type)
387 RadioTrimWidget * trimWidget = new RadioTrimWidget(type == 'H' ? Qt::Horizontal : Qt::Vertical);
388 trimWidget->setIndices(getTrimSliderType(type), getTrimButtonType(type, 0), getTrimButtonType(type, 1));
390 connect(trimWidget, &RadioTrimWidget::valueChange, this, &VirtualJoystickWidget::valueChange);
392 return trimWidget;
395 QToolButton * VirtualJoystickWidget::createButtonWidget(int type)
397 QString btnLabel, tooltip;
398 QIcon icon;
399 switch (type) {
400 case HOLD_Y:
401 btnLabel = tr("Hld Y");
402 tooltip = tr("Hold Vertical stick position.");
403 icon = Simulator::SimulatorIcon("hold_y");
404 break;
405 case FIX_Y:
406 btnLabel = tr("Fix Y");
407 tooltip = tr("Prevent Vertical movement of stick.");
408 icon = Simulator::SimulatorIcon("fixed_y");
409 break;
410 case FIX_X:
411 btnLabel = tr("Fix X");
412 tooltip = tr("Prevent Horizontal movement of stick.");
413 icon = Simulator::SimulatorIcon("fixed_x");
414 break;
415 case HOLD_X:
416 btnLabel = tr("Hld X");
417 tooltip = tr("Hold Horizontal stick position.");
418 icon = Simulator::SimulatorIcon("hold_x");
419 break;
420 default:
421 return NULL;
423 QToolButton * btn = new QToolButton(this);
424 btn->setProperty("btnType", type);
425 btn->setIcon(icon);
426 btn->setIconSize(QSize(20, 20));
427 btn->setToolButtonStyle(Qt::ToolButtonIconOnly);
428 btn->setText(btnLabel);
429 btn->setToolTip(tooltip);
430 btn->setCheckable(true);
431 btn->setAutoRaise(true);
433 connect(btn, &QToolButton::toggled, this, &VirtualJoystickWidget::onButtonChange);
435 return btn;
438 QLayout *VirtualJoystickWidget::createNodeValueLayout(QChar type, QLabel *& valLabel)
440 QLabel * lbl = new QLabel(QString("%1 %").arg(type), this);
441 lbl->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
442 lbl->setAlignment(Qt::AlignCenter);
443 QLabel * val = new QLabel("0");
444 val->setObjectName(QString("val_%1").arg(type));
445 val->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
446 val->setAlignment(Qt::AlignCenter);
447 val->setMinimumWidth(val->fontMetrics().width("-100 "));
448 QVBoxLayout * layout = new QVBoxLayout();
449 layout->setContentsMargins(2, 2, 2, 2);
450 layout->setSpacing(2);
451 layout->addWidget(lbl);
452 layout->addWidget(val);
454 valLabel = val;
455 return layout;
458 int VirtualJoystickWidget::getStickIndex(QChar type)
460 using namespace Board;
462 if (stickSide == 'L') {
463 if (type == 'H')
464 return STICK_AXIS_LH;
465 else
466 return STICK_AXIS_LV;
468 else {
469 if (type == 'H')
470 return STICK_AXIS_RH;
471 else
472 return STICK_AXIS_RV;
476 int VirtualJoystickWidget::getTrimSliderType(QChar type)
478 using namespace Board;
480 if (stickSide == 'L') {
481 if (type == 'H')
482 return TRIM_AXIS_LH;
483 else
484 return TRIM_AXIS_LV;
486 else {
487 if (type == 'H')
488 return TRIM_AXIS_RH;
489 else
490 return TRIM_AXIS_RV;
494 int VirtualJoystickWidget::getTrimButtonType(QChar type, int pos)
496 using namespace Board;
498 if (stickSide == 'L') {
499 if (type == 'H') {
500 if (pos == 0)
501 return TRIM_SW_LH_DEC;
502 else
503 return TRIM_SW_LH_INC;
505 else {
506 if (pos == 0)
507 return TRIM_SW_LV_DEC;
508 else
509 return TRIM_SW_LV_INC;
512 // right side
513 else {
514 if (type == 'H') {
515 if (pos == 0)
516 return TRIM_SW_RH_DEC;
517 else
518 return TRIM_SW_RH_INC;
520 else {
521 if (pos == 0)
522 return TRIM_SW_RV_DEC;
523 else
524 return TRIM_SW_RV_INC;
529 RadioTrimWidget * VirtualJoystickWidget::getTrimWidget(int which)
531 if ((stickSide == 'L' && which == Board::TRIM_AXIS_LH) || (stickSide == 'R' && which == Board::TRIM_AXIS_RH))
532 return hTrimWidget;
533 else if ((stickSide == 'L' && which == Board::TRIM_AXIS_LV) || (stickSide == 'R' && which == Board::TRIM_AXIS_RV))
534 return vTrimWidget;
535 else
536 return NULL;
539 void VirtualJoystickWidget::onButtonChange(bool checked)
541 if (!sender() || !sender()->property("btnType").isValid())
542 return;
544 switch (sender()->property("btnType").toInt()) {
545 case HOLD_Y:
546 node->setCenteringY(!checked);
547 break;
548 case FIX_Y:
549 node->setFixedY(checked);
550 break;
551 case FIX_X:
552 node->setFixedX(checked);
553 break;
554 case HOLD_X:
555 node->setCenteringX(!checked);
556 break;
560 void VirtualJoystickWidget::updateNodeValueLabels()
562 if (nodeLabelX)
563 nodeLabelX->setText(QString("%1").arg(node->getX() * 100.0f, 2, 'f', 0));
564 if (nodeLabelY)
565 nodeLabelY->setText(QString("%1").arg(node->getY() * -100.0f, 2, 'f', 0));
568 void VirtualJoystickWidget::onGsMouseEvent(QGraphicsSceneMouseEvent * event)
570 if (!node)
571 return;
573 //qDebug() << event->type() << event->scenePos() << event->buttons() << event->isAccepted() << m_stickPressed;
574 if (event->type() == QEvent::GraphicsSceneMouseRelease && m_stickPressed) {
575 node->setPressed(false);
576 m_stickPressed = false;
577 return;
580 if (!(event->buttons() & Qt::LeftButton))
581 return;
583 if (event->type() == QEvent::GraphicsSceneMousePress) {
584 node->setPressed(true);
585 m_stickPressed = true;
587 else if (!m_stickPressed || event->type() != QEvent::GraphicsSceneMouseMove) {
588 return;
590 node->setPos(event->scenePos());
595 * CustomGraphicsScene
598 void CustomGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent * event)
600 QGraphicsScene::mousePressEvent(event);
601 if (!event->isAccepted()) {
602 event->accept();
603 emit mouseEvent(event);
607 void CustomGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
609 QGraphicsScene::mouseReleaseEvent(event);
610 emit mouseEvent(event);
613 void CustomGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent * event)
615 QGraphicsScene::mouseMoveEvent(event);
616 if (!event->isAccepted() && (event->buttons() & Qt::LeftButton)) {
617 event->accept();
618 emit mouseEvent(event);