Interactive Directional Control Pad Rendering with Qt
Design a custom diretcional control widget in Qt by defining vector outlines in AutoCAD, aligning coordinate systems, and translating gemoetry into scalable Qt paths.
Coordinate ailgnment requires matching CAD's Y-down orientation to Qt's window system. Once the outline is prepared, subclass QWidget and implement rendering logic that reflects interaction states.
Header Definition
#ifndef DIRECTPADWIDGET_H
#define DIRECTPADWIDGET_H
#include <QWidget>
class DirectPadWidget : public QWidget
{
Q_OBJECT
public:
enum Region {
NegZ,
NegX,
PosZ,
PosX,
PosY,
NegY,
NegRot,
PosRot,
Origin,
Idle
};
explicit DirectPadWidget(QWidget *parent = nullptr);
void activateRegion(Region area);
signals:
void regionActivated(Region area);
protected:
void mousePressEvent(QMouseEvent *evt) override;
void mouseReleaseEvent(QMouseEvent *evt) override;
void mouseMoveEvent(QMouseEvent *evt) override;
void paintEvent(QPaintEvent *evt) override;
void resizeEvent(QResizeEvent *evt) override;
private:
Region currentActive;
Region currentHover;
QColor baseOutlineCol;
QColor activeOutlineCol;
QColor baseFillCol;
QColor activeFillCol;
qreal zoomFactor;
QPainterPath originCirc;
QVector<QPainterPath> sectorPaths;
QVector<QPainterPath> glyphs;
void buildShapes();
};
#endif // DIRECTPADWIDGET_H
Implementation Details
#include "DirectPadWidget.h"
#include <QPainter>
#include <QMouseEvent>
DirectPadWidget::DirectPadWidget(QWidget *parent)
: QWidget(parent)
, currentActive(Idle)
, currentHover(Idle)
, zoomFactor(1.0)
, baseOutlineCol(QColor(200, 200, 200, 100))
, activeOutlineCol(QColor(200, 255, 200, 200))
, baseFillCol(Qt::white)
, activeFillCol(QColor(100, 255, 100))
{
setMouseTracking(true);
setMinimumSize(100, 100);
sectorPaths.resize(8);
glyphs.resize(8);
}
void DirectPadWidget::buildShapes()
{
const qreal ref = 100.0;
QTransform centerTx;
centerTx.translate(width() / 2.0, height() / 2.0);
centerTx.scale(zoomFactor, zoomFactor);
// Example glyph for NegZ
QPainterPath negZGlyph;
negZGlyph.moveTo(0, -ref * 0.85);
negZGlyph.lineTo(ref * 0.20, -ref * 0.75);
negZGlyph.lineTo(ref * 0.10, -ref * 0.75);
negZGlyph.lineTo(ref * 0.10, -ref * 0.55);
negZGlyph.lineTo(-ref * 0.10, -ref * 0.55);
negZGlyph.lineTo(-ref * 0.10, -ref * 0.75);
negZGlyph.lineTo(-ref * 0.20, -ref * 0.75);
negZGlyph.closeSubpath();
// Sector for NegZ
QPainterPath negZSector;
negZSector.moveTo(ref * 0.335, -ref * 0.371);
negZSector.arcTo(-ref * 0.5, -ref * 0.5, ref, ref, 47.87, 84.27);
negZSector.lineTo(-ref * 0.654, -ref * 0.689);
negZSector.arcTo(-ref * 0.95, -ref * 0.95, ref * 1.9, ref * 1.9, 133.49, -86.98);
negZSector.closeSubpath();
// Origin circle
originCirc.addEllipse(-ref * 0.45, -ref * 0.45, ref * 0.9, ref * 0.9);
// Apply transform
glyphs[NegZ] = centerTx.map(negZGlyph);
sectorPaths[NegZ] = centerTx.map(negZSector);
originCirc = centerTx.map(originCirc);
// Rotate for other axes
QTransform rotTx = centerTx;
rotTx.rotate(-90);
glyphs[NegX] = rotTx.map(negZGlyph);
sectorPaths[NegX] = rotTx.map(negZSector);
rotTx.rotate(-90);
glyphs[PosZ] = rotTx.map(negZGlyph);
sectorPaths[PosZ] = rotTx.map(negZSector);
rotTx.rotate(-90);
glyphs[PosX] = rotTx.map(negZGlyph);
sectorPaths[PosX] = rotTx.map(negZSector);
// Additional glyphs for Y axis and rotation
QTransform flipTx = centerTx;
flipTx.scale(zoomFactor, -zoomFactor);
// Populate glyphs/paths for PosY, NegY, PosRot, NegRot accordingly
}
void DirectPadWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::NoPen);
// Base sectors
painter.setBrush(baseOutlineCol);
for (const QPainterPath &p : sectorPaths)
painter.drawPath(p);
painter.drawPath(originCirc);
// Glyphs
painter.setBrush(baseFillCol);
for (const QPainterPath &g : glyphs)
painter.drawPath(g);
// State-based coloring
auto drawHighlighted = [&](Region r) {
if (r >= NegZ && r <= PosRot) {
painter.setBrush(activeOutlineCol);
painter.drawPath(sectorPaths[r]);
painter.setBrush(activeFillCol);
painter.drawPath(glyphs[r]);
} else if (r == Origin) {
painter.setBrush(activeFillCol);
painter.drawPath(originCirc);
}
};
if (currentActive != Idle) {
drawHighlighted(currentActive);
} else if (currentHover != Idle) {
QColor hoverCol = activeFillCol.lighter();
painter.setBrush(hoverCol);
if (currentHover == Origin)
painter.drawPath(originCirc);
else {
painter.setBrush(activeOutlineCol.lighter());
painter.drawPath(sectorPaths[currentHover]);
painter.setBrush(hoverCol);
painter.drawPath(glyphs[currentHover]);
}
}
// Labels
painter.translate(width() / 2.0, height() / 2.0);
painter.scale(zoomFactor, zoomFactor);
QFont f = painter.font();
f.setPixelSize(28);
painter.setFont(f);
painter.setPen(QPen(Qt::black, 2));
painter.drawText(QRectF(-45, -45, 90, 90), Qt::AlignCenter, "ORIGIN");
f.setPixelSize(15);
painter.setFont(f);
painter.drawText(QRectF(-10, -75, 20, 20), Qt::AlignCenter, "Z-");
painter.drawText(QRectF(-75, -10, 20, 20), Qt::AlignCenter, "X-");
painter.drawText(QRectF(-10, 55, 20, 20), Qt::AlignCenter, "Z+");
painter.drawText(QRectF(55, -10, 20, 20), Qt::AlignCenter, "X+");
painter.drawText(QRectF(-85, -85, 15, 10), Qt::AlignCenter, "Y+");
painter.drawText(QRectF(-85, 75, 15, 10), Qt::AlignCenter, "Y-");
painter.drawText(QRectF(66, 80, 15, 10), Qt::AlignCenter, "R-");
painter.drawText(QRectF(66, -90, 15, 10), Qt::AlignCenter, "R+");
}
void DirectPadWidget::resizeEvent(QResizeEvent *)
{
int dim = qMin(width(), height());
zoomFactor = dim / 200.0;
buildShapes();
}
void DirectPadWidget::mousePressEvent(QMouseEvent *evt)
{
if (evt->button() != Qt::LeftButton) return;
QPoint pt = evt->pos();
if (originCirc.contains(pt)) {
activateRegion(Origin);
return;
}
for (int idx = 0; idx < sectorPaths.size(); ++idx) {
if (sectorPaths[idx].contains(pt)) {
activateRegion(static_cast<Region>(idx));
return;
}
}
}
void DirectPadWidget::mouseReleaseEvent(QMouseEvent *)
{
if (currentActive != Origin)
activateRegion(Idle);
}
void DirectPadWidget::mouseMoveEvent(QMouseEvent *evt)
{
if (currentActive != Idle) return;
QPoint pt = evt->pos();
Region over = Idle;
if (originCirc.contains(pt))
over = Origin;
else {
for (int idx = 0; idx < sectorPaths.size(); ++idx) {
if (sectorPaths[idx].contains(pt)) {
over = static_cast<Region>(idx);
break;
}
}
}
if (over != currentHover) {
currentHover = over;
update();
}
}
void DirectPadWidget::activateRegion(Region area)
{
if (area != currentActive) {
currentActive = area;
emit regionActivated(area);
update();
}
}