How can I use a MouseArea on a ShapePath in QML?

1.2k views Asked by At

I'm currently learning how to use the Shapes in QML to draw more advanced components. I'm trying to create a button which looks like this :

My custom button

When I try to apply a MouseArea over the Shape component, the MouseArea does not seem to be able to catch the events on the Shape. Here is my code :

import QtQuick 2.13
import QtQuick.Shapes 1.13

Item
{
    Shape
    {
        id: myShape

        ShapePath {
            id: myButton

            strokeWidth:3.114000082015991
            strokeColor: "#000"
            miterLimit:7
            fillColor: "#ccc"
            capStyle:ShapePath.RoundCap
            PathSvg {
                path: "M392.4,205.9a132.34,132.34,0,0,1,31.7,49.2H575.6a289.67,289.67,0,0,0-12.9-49.2Z"
            }
        }
    }

    MouseArea
    {
        id: myMouseArea
        anchors.fill: myShape
        enabled: true
        hoverEnabled: true

        onEntered: myButton.fillColor = "yellow"
        onExited: myButton.fillColor = "green"
    }
}

So my question is : is it possible to make a Shape/ShapePath clickable in the first place ? And if yes, how to do so ?

1

There are 1 answers

1
JarMan On

A similar question was asked here, but they just wanted a simple circle. Still, the non-accepted answer describes a masked mouse area that could be useful to you. It uses an image to define a masked area. It originally comes from a Qt example program.

maskedmousearea.cpp

MaskedMouseArea::MaskedMouseArea(QQuickItem *parent)
    : QQuickItem(parent),
      m_pressed(false),
      m_alphaThreshold(0.0),
      m_containsMouse(false)
{
    setAcceptHoverEvents(true);
    setAcceptedMouseButtons(Qt::LeftButton);
}

void MaskedMouseArea::setPressed(bool pressed)
{
    if (m_pressed != pressed) {
    m_pressed = pressed;
    emit pressedChanged();
    }
}

void MaskedMouseArea::setContainsMouse(bool containsMouse)
{
    if (m_containsMouse != containsMouse) {
    m_containsMouse = containsMouse;
    emit containsMouseChanged();
    }
}

void MaskedMouseArea::setMaskSource(const QUrl &source)
{
    if (m_maskSource != source) {
    m_maskSource = source;
    m_maskImage = QImage(QQmlFile::urlToLocalFileOrQrc(source));
    emit maskSourceChanged();
    }
}

void MaskedMouseArea::setAlphaThreshold(qreal threshold)
{
    if (m_alphaThreshold != threshold) {
    m_alphaThreshold = threshold;
    emit alphaThresholdChanged();
    }
}

bool MaskedMouseArea::contains(const QPointF &point) const
{
    if (!QQuickItem::contains(point) || m_maskImage.isNull())
    return false;

    QPoint p = point.toPoint();

    if (p.x() < 0 || p.x() >= m_maskImage.width() ||
    p.y() < 0 || p.y() >= m_maskImage.height())
    return false;

    qreal r = qBound<int>(0, m_alphaThreshold * 255, 255);
    return qAlpha(m_maskImage.pixel(p)) > r;
}

void MaskedMouseArea::mousePressEvent(QMouseEvent *event)
{
    setPressed(true);
    m_pressPoint = event->pos();
    emit pressed();
}

void MaskedMouseArea::mouseReleaseEvent(QMouseEvent *event)
{
    setPressed(false);
    emit released();

    const int threshold = qApp->styleHints()->startDragDistance();
    const bool isClick = (threshold >= qAbs(event->x() - m_pressPoint.x()) &&
                      threshold >= qAbs(event->y() - m_pressPoint.y()));

    if (isClick)
    emit clicked();
}

void MaskedMouseArea::mouseUngrabEvent()
{
    setPressed(false);
    emit canceled();
}

void MaskedMouseArea::hoverEnterEvent(QHoverEvent *event)
{
    Q_UNUSED(event);
    setContainsMouse(true);
}

void MaskedMouseArea::hoverLeaveEvent(QHoverEvent *event)
{
    Q_UNUSED(event);
    setContainsMouse(false);
}

Usage in QML:

import Example 1.0
MaskedMouseArea {
    id: moonArea
    anchors.fill: parent
    alphaThreshold: 0.4
    maskSource: moon.source
}

Register the custom item:

qmlRegisterType<MaskedMouseArea>("Example", 1, 0, "MaskedMouseArea");