Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Interactive Directional Control Pad Rendering with Qt

Tech May 11 16

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...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

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