QPainterPathによる可変方向ポインタ付きダイナミックバルーン

以前の記事では固定方向の不規則なバルーンウィンドウについて説明しましたが、今回は、方向を動的に変更できるバルーンウィンドウの実装方法について説明します。

レイアウト構造

このバルーンウィンドウは、2つのレイアウト(水平と垂直)と2つのスペースアイテム、そしてメッセージ表示部から構成されます。以下の図のような構造になります。

図1
図2
図3
図4

UIはQt Designerで作成することも、コードで動的に構築することも可能です。ここではQt Designerで基本レイアウトを作成し、スペースアイテムをコードで追加する方法を採用しています。

コード例(動的レイアウト構築)


switch (direction) {
    case Direction::TopLeft:
        horizontalLayout->addSpacerItem(new QSpacerItem(width() / 8, 0, QSizePolicy::Maximum, QSizePolicy::Minimum));
        horizontalLayout->addWidget(messageWidget);
        horizontalLayout->setStretch(0, 1);
        horizontalLayout->setStretch(1, 7);

        verticalLayout->insertSpacerItem(0, new QSpacerItem(0, height() / 2, QSizePolicy::Minimum, QSizePolicy::Maximum));
        verticalLayout->setStretch(0, 1);
        verticalLayout->setStretch(1, 1);
        break;

    case Direction::BottomLeft:
        horizontalLayout->addSpacerItem(new QSpacerItem(width() / 8, 0, QSizePolicy::Maximum, QSizePolicy::Minimum));
        horizontalLayout->addWidget(messageWidget);
        horizontalLayout->setStretch(0, 1);
        horizontalLayout->setStretch(1, 7);

        verticalLayout->addSpacerItem(new QSpacerItem(0, height() / 2, QSizePolicy::Minimum, QSizePolicy::Maximum));
        verticalLayout->setStretch(0, 1);
        verticalLayout->setStretch(1, 1);
        break;

    case Direction::BottomRight:
        horizontalLayout->addWidget(messageWidget);
        horizontalLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
        horizontalLayout->setStretch(0, 7);
        horizontalLayout->setStretch(1, 1);

        verticalLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding));
        verticalLayout->setStretch(0, 1);
        verticalLayout->setStretch(1, 1);
        break;

    case Direction::TopRight:
        horizontalLayout->addWidget(messageWidget);
        horizontalLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
        horizontalLayout->setStretch(0, 7);
        horizontalLayout->setStretch(1, 1);

        verticalLayout->insertSpacerItem(0, new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding));
        verticalLayout->setStretch(0, 1);
        verticalLayout->setStretch(1, 1);
        break;
}

背景形状の描画

背景を透過しているため、手動で背景形状を描画する必要があります。以下のコードで背景矩形と矢印三角形を生成します:


QPainterPath createBalloonPath(const QSize& size, int style) {
    QPainterPath rectPath, arrowPath;

    switch (style) {
        case 1: // 左上
            rectPath.addRoundRect(QRect(size.width() / 8, size.height() / 2, size.width() * 7 / 8, size.height() / 2), 10);
            arrowPath.moveTo(0, 0);
            arrowPath.lineTo(size.width() / 4, size.height() / 2);
            arrowPath.lineTo(size.width() * 3 / 8, size.height() / 2);
            arrowPath.lineTo(0, 0);
            break;

        case 2: // 右上
            rectPath.addRoundRect(QRect(0, size.height() / 2, size.width() * 7 / 8, size.height() / 2), 10);
            arrowPath.moveTo(size.width(), 0);
            arrowPath.lineTo(size.width() * 3 / 4, size.height() / 2);
            arrowPath.lineTo(size.width() * 5 / 8, size.height() / 2);
            arrowPath.lineTo(size.width(), 0);
            break;

        case 3: // 右下
            rectPath.addRoundRect(QRect(0, 0, size.width() * 7 / 8, size.height() / 2), 10);
            arrowPath.moveTo(size.width(), size.height());
            arrowPath.lineTo(size.width() * 3 / 4, size.height() / 2);
            arrowPath.lineTo(size.width() * 5 / 8, size.height() / 2);
            arrowPath.lineTo(size.width(), size.height());
            break;

        case 4: // 左下
            rectPath.addRoundRect(QRect(size.width() / 8, 0, size.width() * 7 / 8, size.height() / 2), 10);
            arrowPath.moveTo(0, size.height());
            arrowPath.lineTo(size.width() / 4, size.height() / 2);
            arrowPath.lineTo(size.width() * 3 / 8, size.height() / 2);
            arrowPath.lineTo(0, size.height());
            break;
    }

    rectPath.addPath(arrowPath);
    return rectPath;
}

代替実装方法:画像ベースの描画

別の方法として、QImageに一度描画してからウィンドウに描画する方法があります。この方法では、画像に対して回転やミラーリングなどの変換を適用できます。


void customPaintEvent(QPaintEvent*) {
    QImage image(size(), QImage::Format_ARGB32_Premultiplied);
    image.fill(Qt::transparent);

    QPainter imagePainter(&image);
    QPainterPath balloonPath = createBalloonPath(size(), currentStyle);
    
    imagePainter.setRenderHint(QPainter::Antialiasing);
    imagePainter.fillPath(balloonPath, Qt::white);
    
    QPainter windowPainter(this);
    windowPainter.drawImage(0, 0, image);
}

タグ: Qt QPainter QPainterPath QWidget QLayout

7月24日 02:06 投稿