mirror of
https://github.com/dolphin-emu/dolphin
synced 2025-10-06 00:13:03 +02:00
Merge pull request #12329 from Dentomologist/balloontip_fix_premature_close_on_balloontip_hover
BalloonTip: Don't hide when the BalloonTip blocks the cursor
This commit is contained in:
@@ -5,11 +5,11 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBitmap>
|
||||
#include <QBrush>
|
||||
#include <QCursor>
|
||||
#include <QFont>
|
||||
#include <QGuiApplication>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
@@ -25,11 +25,17 @@
|
||||
#include <QToolTip>
|
||||
#endif
|
||||
|
||||
#include "DolphinQt/QtUtils/QueueOnObject.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unique_ptr<BalloonTip> s_the_balloon_tip = nullptr;
|
||||
// Remember the parent ToolTipWidget so cursor-related events can see whether the cursor is inside
|
||||
// the parent's bounding box or not. Use this variable instead of BalloonTip's parent() member
|
||||
// because the ToolTipWidget isn't responsible for deleting the BalloonTip and so doesn't set its
|
||||
// parent member.
|
||||
QWidget* s_parent = nullptr;
|
||||
} // namespace
|
||||
|
||||
void BalloonTip::ShowBalloon(const QString& title, const QString& message,
|
||||
@@ -53,6 +59,7 @@ void BalloonTip::ShowBalloon(const QString& title, const QString& message,
|
||||
|
||||
void BalloonTip::HideBalloon()
|
||||
{
|
||||
s_parent = nullptr;
|
||||
#if defined(__APPLE__)
|
||||
QToolTip::hideText();
|
||||
#else
|
||||
@@ -66,6 +73,9 @@ void BalloonTip::HideBalloon()
|
||||
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent)
|
||||
: QWidget(nullptr, Qt::ToolTip)
|
||||
{
|
||||
s_parent = parent;
|
||||
setMouseTracking(true);
|
||||
|
||||
QColor window_color;
|
||||
QColor text_color;
|
||||
QColor dolphin_emphasis;
|
||||
@@ -113,10 +123,61 @@ BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidge
|
||||
create_label(message);
|
||||
}
|
||||
|
||||
void BalloonTip::paintEvent(QPaintEvent*)
|
||||
bool BalloonTip::IsCursorInsideWidgetBoundingBox(const QWidget& widget)
|
||||
{
|
||||
const QPoint local_cursor_position = widget.mapFromGlobal(QCursor::pos());
|
||||
return widget.rect().contains(local_cursor_position);
|
||||
}
|
||||
|
||||
bool BalloonTip::IsCursorOnBalloonTip()
|
||||
{
|
||||
return s_the_balloon_tip != nullptr &&
|
||||
QApplication::widgetAt(QCursor::pos()) == s_the_balloon_tip.get();
|
||||
}
|
||||
|
||||
bool BalloonTip::IsWidgetBalloonTipActive(const QWidget& widget)
|
||||
{
|
||||
return &widget == s_parent;
|
||||
}
|
||||
|
||||
// Hiding the balloon causes the BalloonTip widget to be deleted. Triggering that deletion while
|
||||
// inside a BalloonTip event handler leads to a use-after-free crash or worse, so queue the deletion
|
||||
// for later.
|
||||
static void QueueHideBalloon()
|
||||
{
|
||||
QueueOnObject(s_parent, BalloonTip::HideBalloon);
|
||||
}
|
||||
|
||||
void BalloonTip::enterEvent(QEnterEvent* const event)
|
||||
{
|
||||
if (!IsCursorInsideWidgetBoundingBox(*s_parent))
|
||||
QueueHideBalloon();
|
||||
|
||||
QWidget::enterEvent(event);
|
||||
}
|
||||
|
||||
void BalloonTip::mouseMoveEvent(QMouseEvent* const event)
|
||||
{
|
||||
if (!IsCursorInsideWidgetBoundingBox(*s_parent))
|
||||
QueueHideBalloon();
|
||||
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void BalloonTip::leaveEvent(QEvent* const event)
|
||||
{
|
||||
if (QApplication::widgetAt(QCursor::pos()) != s_parent)
|
||||
QueueHideBalloon();
|
||||
|
||||
QWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
void BalloonTip::paintEvent(QPaintEvent* const event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.drawPixmap(rect(), m_pixmap);
|
||||
|
||||
QWidget::paintEvent(event);
|
||||
}
|
||||
|
||||
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
|
@@ -7,6 +7,9 @@
|
||||
#include <QPixmap>
|
||||
#include <QWidget>
|
||||
|
||||
class QEnterEvent;
|
||||
class QEvent;
|
||||
class QMouseEvent;
|
||||
class QPaintEvent;
|
||||
class QPoint;
|
||||
class QString;
|
||||
@@ -29,17 +32,22 @@ public:
|
||||
const QPoint& target_arrow_tip_position, QWidget* parent,
|
||||
ShowArrow show_arrow = ShowArrow::Yes, int border_width = 1);
|
||||
static void HideBalloon();
|
||||
static bool IsCursorInsideWidgetBoundingBox(const QWidget& widget);
|
||||
static bool IsCursorOnBalloonTip();
|
||||
static bool IsWidgetBalloonTipActive(const QWidget& widget);
|
||||
|
||||
BalloonTip(PrivateTag, const QString& title, QString message, QWidget* parent);
|
||||
|
||||
protected:
|
||||
void enterEvent(QEnterEvent* event) override;
|
||||
void leaveEvent(QEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow,
|
||||
int border_width);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*) override;
|
||||
|
||||
private:
|
||||
QColor m_border_color;
|
||||
QPixmap m_pixmap;
|
||||
};
|
||||
|
@@ -9,6 +9,11 @@
|
||||
|
||||
#include "DolphinQt/Config/ToolTipControls/BalloonTip.h"
|
||||
|
||||
class QEnterEvent;
|
||||
class QEvent;
|
||||
class QHideEvent;
|
||||
class QTimerEvent;
|
||||
|
||||
constexpr int TOOLTIP_DELAY = 300;
|
||||
|
||||
template <class Derived>
|
||||
@@ -22,28 +27,48 @@ public:
|
||||
void SetDescription(QString description) { m_description = std::move(description); }
|
||||
|
||||
private:
|
||||
void enterEvent(QEnterEvent* event) override
|
||||
void enterEvent(QEnterEvent* const event) override
|
||||
{
|
||||
if (m_timer_id)
|
||||
return;
|
||||
m_timer_id = this->startTimer(TOOLTIP_DELAY);
|
||||
// If the timer is already running, or the cursor is reentering the ToolTipWidget after having
|
||||
// hovered over the BalloonTip, don't start a new timer.
|
||||
if (!m_timer_id && !BalloonTip::IsWidgetBalloonTipActive(*this))
|
||||
m_timer_id = this->startTimer(TOOLTIP_DELAY);
|
||||
|
||||
Derived::enterEvent(event);
|
||||
}
|
||||
|
||||
void leaveEvent(QEvent* event) override { KillAndHide(); }
|
||||
void hideEvent(QHideEvent* event) override { KillAndHide(); }
|
||||
void leaveEvent(QEvent* const event) override
|
||||
{
|
||||
// If the cursor would still be inside the ToolTipWidget but the BalloonTip is covering that
|
||||
// part of it, keep the BalloonTip open. In that case the BalloonTip will then track the cursor
|
||||
// and close itself if it leaves the bounding box of this ToolTipWidget.
|
||||
if (!BalloonTip::IsCursorInsideWidgetBoundingBox(*this) || !BalloonTip::IsCursorOnBalloonTip())
|
||||
KillTimerAndHideBalloon();
|
||||
|
||||
void timerEvent(QTimerEvent* event) override
|
||||
Derived::leaveEvent(event);
|
||||
}
|
||||
|
||||
void hideEvent(QHideEvent* const event) override
|
||||
{
|
||||
KillTimerAndHideBalloon();
|
||||
|
||||
Derived::hideEvent(event);
|
||||
}
|
||||
|
||||
void timerEvent(QTimerEvent* const event) override
|
||||
{
|
||||
this->killTimer(*m_timer_id);
|
||||
m_timer_id.reset();
|
||||
|
||||
BalloonTip::ShowBalloon(m_title, m_description,
|
||||
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
|
||||
|
||||
Derived::timerEvent(event);
|
||||
}
|
||||
|
||||
virtual QPoint GetToolTipPosition() const = 0;
|
||||
|
||||
void KillAndHide()
|
||||
void KillTimerAndHideBalloon()
|
||||
{
|
||||
if (m_timer_id)
|
||||
{
|
||||
|
Reference in New Issue
Block a user