2018年4月6日金曜日

MouseArea のマスク

Item の containmentMask にマスクオブジェクトを設定すると MouseArea のイベントをマスクできます。マスクオブジェクトとは以下を実装した QObject を継承したオブジェクトです。
Q_INVOKABLE QObject::contains(const QPoint& point)
QObject を継承したマスクオブジェクトは以下のようにして作成します。
main.cpp:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QLineF>

class CircleMask : public QObject
{
    Q_OBJECT

    Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)

public:
    explicit CircleMask(QObject* parent = 0) : QObject(parent) {}

    ~CircleMask() {}

    qreal radius() const {
        return d.radius;
    }

    void setRadius(qreal radius)
    {
        if (d.radius == radius) {
            return;
        }
        d.radius = radius;
        emit radiusChanged();
    }

    Q_INVOKABLE bool contains(const QPointF& point) const
    {
        const QPointF center(d.radius, d.radius);
        const QLineF line(center, point);
        return line.length() <= d.radius;
    }

signals:
    void radiusChanged();

private:
    struct {
        qreal radius;
    } d;
};

int main(int argc, char** argv)
{
    QGuiApplication app(argc, argv);

    qmlRegisterType("Mask", 1, 0, "CircleMask");

    QQmlApplicationEngine engine;
    engine.load(QUrl("qrc:/main.qml"));

    return app.exec();
}

#include "main.moc"
contains() では point が半径 radius の円の内側かどうかを判定しています。
mail.qml:

import QtQuick 2.11
import QtQuick.Window 2.10
import Mask 1.0

Window {
    id: window

    visible: true

    minimumWidth: 300
    minimumHeight: 300

    color: "white"

    Rectangle {
        id: circle

        width: window.width/2; height: width
        anchors.centerIn: parent

        radius: width/2
        color: "red"
        border.width: circleMouseArea.containsMouse ? 4 : 0
        border.color: "firebrick"

        MouseArea {
            id: circleMouseArea

            anchors.fill: parent

            hoverEnabled: true
            containmentMask: CircleMask {
                radius: circle.radius
            }

            onPressed: {
                parent.color = "green";
            }

            onReleased: {
                parent.color = "red";
            }
        }
    }
}
MouseArea の containmentMask に半径を設定した CircleMask オブジェクトを設定すれば円の内側だけでマウスが反応するようになります。

円の内側だけでマウスが反応する

Qt Quick Shapes の Shape をオブジェクトマスクに使えます。containsMode を Shape.FillContains に設定すると内側だけに反応するようになります。
shapedmousearea.qml:

import QtQuick 2.11
import QtQuick.Shapes 1.11
import QtQuick.Window 2.10

Window {
    id: window

    visible: true

    minimumWidth: 300
    minimumHeight: 300

    color: "white"

    Shape {
        id: circle

        readonly property real radius: 60

        anchors.fill: parent

        layer.enabled: true
        layer.smooth: true
        layer.textureSize: Qt.size(2*width, 2*height)
        vendorExtensionsEnabled: false

        containsMode: Shape.FillContains

        ShapePath {
            id: halfArch

            strokeWidth: -1
            fillColor: "green"

            startX: circle.width/2 - circle.radius
            startY: circle.height/2 - circle.radius

            PathArc {
                id: halfPathArc

                x: circle.width/2 + circle.radius
                y: circle.height/2 + circle.radius
                radiusX: circle.radius
                radiusY: circle.radius
            }
        }
    }

    MouseArea {
        id: circleMouseArea

        anchors.fill: circle

        containmentMask: circle

        onPressed: {
            halfArch.fillColor = "red";
        }

        onReleased: {
            halfArch.fillColor = "green";
        }
    }
}
こちらも MouseArea の containmentMask に Shape オブジェクトをマスクとして設定します。こうすると半円の内側だけでマウスに反応します。Qt Quick Shapes を使えば不定形図形の描画とマスクに同じ Shape オブジェクトを使えます。

半円の内側だけでマウスに反応する


参考情報

containmentMask

Trolltech Colors

Qt の有名な 2 色を使ってみます。
Trolltech Green RGB(141, 192, 48) [CMYK 40/0/100/0]
Trolltech Purple RGB(131, 124, 172) [CMYK 39/39/0/0]
QML での実装です。
trolltechcolors.qml:

import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.Layouts 1.3

Window {
    id: window

    visible: true

    readonly property color trolltechGreen: Qt.rgba(141/255, 192/255, 48/255)
    readonly property color trolltechPurple: Qt.rgba(131/255, 124/255, 172/255)

    minimumWidth: topLayout.implicitWidth
    minimumHeight: topLayout.implicitHeight

    ColumnLayout {
        id: topLayout

        anchors.fill: parent
        spacing: 0

        Repeater {
            id: trolltechColors

            model: [ { "name": "Trolltech Green", "color": trolltechGreen },
                     { "name": "Trolltech Purple", "color": trolltechPurple } ]

            Rectangle {
                width: 2*fontMetrics.boundingRect(name.text).width
                height: 3*fontMetrics.height
                Layout.fillWidth: true
                Layout.fillHeight: true

                color: modelData.color

                Text {
                    id: name

                    anchors.centerIn: parent

                    text: modelData.name
                    color: "white"

                    FontMetrics {
                        id: fontMetrics

                        font: name.font
                    }
                }
            }
        }
    }
}
FontMetrics は Text の子オブジェクトですが Item を継承していないので parent プロパティーはありません。従って、Text の font を参照するには id 修飾が必要です。

C++ での実装です。
main.cpp:

#include <QApplication>
#include <QLabel>
#include <QPalette>
#include <QFontMetricsF>
#include <QLayout>

class TrolltechColors : public QWidget
{
    Q_OBJECT

public:
    TrolltechColors();

private:
    QLabel* makeColorLabel(const QString& text, const QColor& color) const;
};

TrolltechColors::TrolltechColors()
    : QWidget(0)
{
    const auto topLayout = new QVBoxLayout(this);
    topLayout->setMargin(0);
    topLayout->setSpacing(0);
    topLayout->addWidget(makeColorLabel("Trolltech Green", QColor(141, 192, 48)));
    topLayout->addWidget(makeColorLabel("Trolltech Purple", QColor(131, 124, 172)));
}

QLabel* TrolltechColors::makeColorLabel(const QString& text, const QColor& color) const
{
    const auto label = new QLabel(text);
    label->setAutoFillBackground(true);

    QPalette labelPalette = label->palette();
    labelPalette.setColor(QPalette::Window, color);
    labelPalette.setColor(QPalette::WindowText, "White");
    label->setPalette(labelPalette);

    const QFontMetricsF fontMetricsF(label->font());

    label->setMinimumWidth(2*fontMetricsF.width(label->text()));
    label->setMinimumHeight(3*fontMetricsF.height());

    label->setAlignment(Qt::AlignCenter);

    return label;
}

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    TrolltechColors trolltechColors;
    trolltechColors.show();

    app.exec();
}

#include "main.moc"
QLabel::fontMetrics() は QFontMetrics を返すので QML が使っている QFontMetricsF よりも幅や高さが大きくなります。そこで QFontMetricsF を使って同じサイズになるようにしています。

実行結果は左から順に QML で renderType にデフォルトの Text.QtRendering と Text.NativeRendering、QLabel で QFontMetricsF と QLabel::fontMetrics() です。QML の Text.NativeRendering は QLable と同じようにテキスト描画されます。QML の Text.QtRendering ではこれらと違いテキストが細くなっています。しかし、FontMetrics で求めるサイズは renderType に依存しません。

QML
renderType: Text.QtRendering
QML
renderType: Text.NativeRendering
QLabel
QFontMetricsF
QLabel
fontMetrics()

索引

索引