dolphin/Source/Core/DolphinQt/Config/Graphics/BalloonTip.cpp

293 lines
8.9 KiB
C++

// Copyright 2020 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt/Config/Graphics/BalloonTip.h"
#include <memory>
#include <QBitmap>
#include <QGraphicsEffect>
#include <QGraphicsView>
#include <QGridLayout>
#include <QGuiApplication>
#include <QLabel>
#include <QPainter>
#include <QPainterPath>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QStyle>
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
#include <QScreen>
#else
#include <QApplication>
#include <QDesktopWidget>
#endif
#if defined(__APPLE__)
#include <QToolTip>
#endif
#include "Core/Config/MainSettings.h"
namespace
{
std::unique_ptr<BalloonTip> s_the_balloon_tip = nullptr;
} // namespace
void BalloonTip::ShowBalloon(const QIcon& icon, const QString& title, const QString& message,
const QPoint& pos, QWidget* parent, ShowArrow show_arrow)
{
HideBalloon();
if (message.isEmpty() && title.isEmpty())
return;
#if defined(__APPLE__)
QString the_message = message;
the_message.replace(QStringLiteral("<dolphin_emphasis>"), QStringLiteral("<b>"));
the_message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b>"));
QToolTip::showText(pos, the_message, parent);
#else
s_the_balloon_tip = std::make_unique<BalloonTip>(PrivateTag{}, icon, title, message, parent);
s_the_balloon_tip->UpdateBoundsAndRedraw(pos, show_arrow);
#endif
}
void BalloonTip::HideBalloon()
{
#if defined(__APPLE__)
QToolTip::hideText();
#else
if (!s_the_balloon_tip)
return;
s_the_balloon_tip->hide();
s_the_balloon_tip.reset();
#endif
}
BalloonTip::BalloonTip(PrivateTag, const QIcon& icon, QString title, QString message,
QWidget* parent)
: QWidget(nullptr, Qt::ToolTip)
{
setAttribute(Qt::WA_DeleteOnClose);
setAutoFillBackground(true);
const QPalette& pal = parent->palette();
const auto theme_window_color = pal.color(QPalette::Base);
const auto theme_window_hsv = theme_window_color.toHsv();
const auto brightness = theme_window_hsv.value();
QColor window_color;
QColor text_color;
QColor dolphin_emphasis;
const bool use_high_contrast = Config::Get(Config::MAIN_USE_HIGH_CONTRAST_TOOLTIPS);
if (brightness > 128)
{
if (use_high_contrast)
{
// Our theme color is light, so make it darker
window_color = QColor(72, 72, 72);
text_color = Qt::white;
dolphin_emphasis = Qt::yellow;
m_border_color = palette().color(QPalette::Window).darker(160);
}
else
{
window_color = pal.color(QPalette::Window);
text_color = pal.color(QPalette::Text);
dolphin_emphasis = QColor(QStringLiteral("#0090ff"));
m_border_color = pal.color(QPalette::Text);
}
}
else
{
if (use_high_contrast)
{
// Our theme color is dark, so make it lighter
window_color = Qt::white;
text_color = Qt::black;
dolphin_emphasis = QColor(QStringLiteral("#0090ff"));
m_border_color = palette().color(QPalette::Window).darker(160);
}
else
{
window_color = pal.color(QPalette::Window);
text_color = pal.color(QPalette::Text);
dolphin_emphasis = Qt::yellow;
m_border_color = pal.color(QPalette::Text);
}
}
const auto style_sheet = QStringLiteral("background-color: #%1; color: #%2;")
.arg(window_color.rgba(), 0, 16)
.arg(text_color.rgba(), 0, 16);
setStyleSheet(style_sheet);
// Replace text in our our message
// if specific "tags" are used
message.replace(QStringLiteral("<dolphin_emphasis>"),
QStringLiteral("<font color=\"#%1\"><b>").arg(dolphin_emphasis.rgba(), 0, 16));
message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b></font>"));
auto* title_label = new QLabel;
title_label->installEventFilter(this);
title_label->setText(title);
QFont f = title_label->font();
f.setBold(true);
title_label->setFont(f);
title_label->setTextFormat(Qt::RichText);
title_label->setSizePolicy(QSizePolicy::Policy::MinimumExpanding,
QSizePolicy::Policy::MinimumExpanding);
auto* message_label = new QLabel;
message_label->installEventFilter(this);
message_label->setText(message);
message_label->setTextFormat(Qt::RichText);
message_label->setAlignment(Qt::AlignTop | Qt::AlignLeft);
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
const int limit = QApplication::desktop()->availableGeometry(message_label).width() / 3;
#else
const int limit = message_label->screen()->availableGeometry().width() / 3;
#endif
message_label->setMaximumWidth(limit);
message_label->setSizePolicy(QSizePolicy::Policy::MinimumExpanding,
QSizePolicy::Policy::MinimumExpanding);
if (message_label->sizeHint().width() > limit)
{
message_label->setWordWrap(true);
}
auto* layout = new QGridLayout;
layout->addWidget(title_label, 0, 0, 1, 2);
layout->addWidget(message_label, 1, 0, 1, 3);
layout->setSizeConstraint(QLayout::SetMinimumSize);
setLayout(layout);
}
void BalloonTip::paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.drawPixmap(rect(), m_pixmap);
}
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& pos, ShowArrow show_arrow)
{
m_show_arrow = show_arrow == ShowArrow::Yes;
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
const QRect screen_rect = QApplication::desktop()->screenGeometry(pos);
#else
QScreen* screen = QGuiApplication::screenAt(pos);
if (!screen)
screen = QGuiApplication::primaryScreen();
const QRect screen_rect = screen->geometry();
#endif
QSize sh = sizeHint();
const int border = 1;
const int arrow_height = 18;
const int arrow_width = 18;
const int arrow_offset = 52;
const int rect_center = 7;
const bool arrow_at_bottom = (pos.y() - sh.height() - arrow_height > 0);
const bool arrow_at_left = (pos.x() + sh.width() - arrow_width < screen_rect.width());
const int default_padding = 10;
layout()->setContentsMargins(border + 3 + default_padding,
border + (arrow_at_bottom ? 0 : arrow_height) + 2 + default_padding,
border + 3 + default_padding,
border + (arrow_at_bottom ? arrow_height : 0) + 2 + default_padding);
updateGeometry();
sh = sizeHint();
int ml, mr, mt, mb;
QSize sz = sizeHint();
if (arrow_at_bottom)
{
ml = mt = 0;
mr = sz.width() - 1;
mb = sz.height() - arrow_height - 1;
}
else
{
ml = 0;
mt = arrow_height;
mr = sz.width() - 1;
mb = sz.height() - 1;
}
QPainterPath path;
path.moveTo(ml + rect_center, mt);
if (!arrow_at_bottom && arrow_at_left)
{
if (m_show_arrow)
{
path.lineTo(ml + arrow_offset - arrow_width, mt);
path.lineTo(ml + arrow_offset, mt - arrow_height);
path.lineTo(ml + arrow_offset + arrow_width, mt);
}
move(qMax(pos.x() - arrow_offset, screen_rect.left() + 2), pos.y());
}
else if (!arrow_at_bottom && !arrow_at_left)
{
if (m_show_arrow)
{
path.lineTo(mr - arrow_offset - arrow_width, mt);
path.lineTo(mr - arrow_offset, mt - arrow_height);
path.lineTo(mr - arrow_offset + arrow_width, mt);
}
move(qMin(pos.x() - sh.width() + arrow_offset, screen_rect.right() - sh.width() - 2), pos.y());
}
path.lineTo(mr - rect_center, mt);
path.arcTo(QRect(mr - rect_center * 2, mt, rect_center * 2, rect_center * 2), 90, -90);
path.lineTo(mr, mb - rect_center);
path.arcTo(QRect(mr - rect_center * 2, mb - rect_center * 2, rect_center * 2, rect_center * 2), 0,
-90);
if (arrow_at_bottom && !arrow_at_left)
{
if (m_show_arrow)
{
path.lineTo(mr - arrow_offset + arrow_width, mb);
path.lineTo(mr - arrow_offset, mb + arrow_height);
path.lineTo(mr - arrow_offset - arrow_width, mb);
}
move(qMin(pos.x() - sh.width() + arrow_offset, screen_rect.right() - sh.width() - 2),
pos.y() - sh.height());
}
else if (arrow_at_bottom && arrow_at_left)
{
if (m_show_arrow)
{
path.lineTo(arrow_offset + arrow_width, mb);
path.lineTo(arrow_offset, mb + arrow_height);
path.lineTo(arrow_offset - arrow_width, mb);
}
move(qMax(pos.x() - arrow_offset, screen_rect.x() + 2), pos.y() - sh.height());
}
path.lineTo(ml + rect_center, mb);
path.arcTo(QRect(ml, mb - rect_center * 2, rect_center * 2, rect_center * 2), -90, -90);
path.lineTo(ml, mt + rect_center);
path.arcTo(QRect(ml, mt, rect_center * 2, rect_center * 2), 180, -90);
// Set the mask
QBitmap bitmap(sizeHint());
bitmap.fill(Qt::color0);
QPainter painter1(&bitmap);
painter1.setPen(QPen(Qt::color1, border));
painter1.setBrush(QBrush(Qt::color1));
painter1.drawPath(path);
setMask(bitmap);
// Draw the border
m_pixmap = QPixmap(sz);
QPainter painter2(&m_pixmap);
painter2.setPen(QPen(m_border_color));
painter2.setBrush(palette().color(QPalette::Window));
painter2.drawPath(path);
show();
}