Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Interactive Directional Control Pad Rendering with Qt

Tech May 11 6

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();
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.