以前の記事では固定方向の不規則なバルーンウィンドウについて説明しましたが、今回は、方向を動的に変更できるバルーンウィンドウの実装方法について説明します。
レイアウト構造
このバルーンウィンドウは、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);
}