From c754b02aae19919b42e77701dc4c017f4a519158 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Sat, 17 Oct 2020 00:05:00 -0500 Subject: [PATCH] DolphinQt: Add BalloonTip which is built off of an internal Qt class. It gives the ability to show a tooltip with an arrow! --- Source/Core/DolphinQt/CMakeLists.txt | 2 + .../DolphinQt/Config/Graphics/BalloonTip.cpp | 269 ++++++++++++++++++ .../DolphinQt/Config/Graphics/BalloonTip.h | 42 +++ Source/Core/DolphinQt/DolphinQt.vcxproj | 2 + 4 files changed, 315 insertions(+) create mode 100644 Source/Core/DolphinQt/Config/Graphics/BalloonTip.cpp create mode 100644 Source/Core/DolphinQt/Config/Graphics/BalloonTip.h diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index bbf7890da1..d059eb83fc 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -81,6 +81,8 @@ add_executable(dolphin-emu Config/GeckoCodeWidget.h Config/Graphics/AdvancedWidget.cpp Config/Graphics/AdvancedWidget.h + Config/Graphics/BalloonTip.cpp + Config/Graphics/BalloonTip.h Config/Graphics/EnhancementsWidget.cpp Config/Graphics/EnhancementsWidget.h Config/Graphics/GeneralWidget.cpp diff --git a/Source/Core/DolphinQt/Config/Graphics/BalloonTip.cpp b/Source/Core/DolphinQt/Config/Graphics/BalloonTip.cpp new file mode 100644 index 0000000000..6be2c637f0 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/BalloonTip.cpp @@ -0,0 +1,269 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/BalloonTip.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) +#include +#else +#include +#include +#endif + +#if defined(__APPLE__) +#include +#endif + +namespace +{ +std::unique_ptr 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(""), QStringLiteral("")); + the_message.replace(QStringLiteral(""), QStringLiteral("")); + QToolTip::showText(pos, the_message, parent); +#else + s_the_balloon_tip = std::make_unique(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; + if (brightness > 128) + { + // 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 + { + // 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); + } + + 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(""), + QStringLiteral("").arg(dolphin_emphasis.rgba(), 0, 16)); + message.replace(QStringLiteral(""), QStringLiteral("")); + + 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(); +} diff --git a/Source/Core/DolphinQt/Config/Graphics/BalloonTip.h b/Source/Core/DolphinQt/Config/Graphics/BalloonTip.h new file mode 100644 index 0000000000..0bfb013f3c --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/BalloonTip.h @@ -0,0 +1,42 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +class BalloonTip : public QWidget +{ + Q_OBJECT + + struct PrivateTag + { + }; + +public: + enum class ShowArrow + { + Yes, + No + }; + static void ShowBalloon(const QIcon& icon, const QString& title, const QString& msg, + const QPoint& pos, QWidget* parent, + ShowArrow show_arrow = ShowArrow::Yes); + static void HideBalloon(); + + BalloonTip(PrivateTag, const QIcon& icon, QString title, QString msg, QWidget* parent); + +private: + void UpdateBoundsAndRedraw(const QPoint&, ShowArrow); + +protected: + void paintEvent(QPaintEvent*) override; + +private: + QColor m_border_color; + QPixmap m_pixmap; + bool m_show_arrow = true; +}; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 04f86f2015..56edeccf7a 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -60,6 +60,7 @@ + @@ -222,6 +223,7 @@ +