mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-10-21 15:39:43 +00: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:
commit
cbdb7ac38e
3 changed files with 108 additions and 14 deletions
|
@ -5,11 +5,11 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
#include <QBitmap>
|
#include <QBitmap>
|
||||||
#include <QBrush>
|
#include <QBrush>
|
||||||
#include <QCursor>
|
#include <QCursor>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QGuiApplication>
|
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPainterPath>
|
#include <QPainterPath>
|
||||||
|
@ -25,11 +25,17 @@
|
||||||
#include <QToolTip>
|
#include <QToolTip>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "DolphinQt/QtUtils/QueueOnObject.h"
|
||||||
#include "DolphinQt/Settings.h"
|
#include "DolphinQt/Settings.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
std::unique_ptr<BalloonTip> s_the_balloon_tip = nullptr;
|
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
|
} // namespace
|
||||||
|
|
||||||
void BalloonTip::ShowBalloon(const QString& title, const QString& message,
|
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()
|
void BalloonTip::HideBalloon()
|
||||||
{
|
{
|
||||||
|
s_parent = nullptr;
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
QToolTip::hideText();
|
QToolTip::hideText();
|
||||||
#else
|
#else
|
||||||
|
@ -66,6 +73,9 @@ void BalloonTip::HideBalloon()
|
||||||
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent)
|
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent)
|
||||||
: QWidget(nullptr, Qt::ToolTip)
|
: QWidget(nullptr, Qt::ToolTip)
|
||||||
{
|
{
|
||||||
|
s_parent = parent;
|
||||||
|
setMouseTracking(true);
|
||||||
|
|
||||||
QColor window_color;
|
QColor window_color;
|
||||||
QColor text_color;
|
QColor text_color;
|
||||||
QColor dolphin_emphasis;
|
QColor dolphin_emphasis;
|
||||||
|
@ -113,10 +123,61 @@ BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidge
|
||||||
create_label(message);
|
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);
|
QPainter painter(this);
|
||||||
painter.drawPixmap(rect(), m_pixmap);
|
painter.drawPixmap(rect(), m_pixmap);
|
||||||
|
|
||||||
|
QWidget::paintEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QEnterEvent;
|
||||||
|
class QEvent;
|
||||||
|
class QMouseEvent;
|
||||||
class QPaintEvent;
|
class QPaintEvent;
|
||||||
class QPoint;
|
class QPoint;
|
||||||
class QString;
|
class QString;
|
||||||
|
@ -29,17 +32,22 @@ public:
|
||||||
const QPoint& target_arrow_tip_position, QWidget* parent,
|
const QPoint& target_arrow_tip_position, QWidget* parent,
|
||||||
ShowArrow show_arrow = ShowArrow::Yes, int border_width = 1);
|
ShowArrow show_arrow = ShowArrow::Yes, int border_width = 1);
|
||||||
static void HideBalloon();
|
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);
|
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:
|
private:
|
||||||
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow,
|
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow,
|
||||||
int border_width);
|
int border_width);
|
||||||
|
|
||||||
protected:
|
|
||||||
void paintEvent(QPaintEvent*) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QColor m_border_color;
|
QColor m_border_color;
|
||||||
QPixmap m_pixmap;
|
QPixmap m_pixmap;
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,11 @@
|
||||||
|
|
||||||
#include "DolphinQt/Config/ToolTipControls/BalloonTip.h"
|
#include "DolphinQt/Config/ToolTipControls/BalloonTip.h"
|
||||||
|
|
||||||
|
class QEnterEvent;
|
||||||
|
class QEvent;
|
||||||
|
class QHideEvent;
|
||||||
|
class QTimerEvent;
|
||||||
|
|
||||||
constexpr int TOOLTIP_DELAY = 300;
|
constexpr int TOOLTIP_DELAY = 300;
|
||||||
|
|
||||||
template <class Derived>
|
template <class Derived>
|
||||||
|
@ -22,28 +27,48 @@ public:
|
||||||
void SetDescription(QString description) { m_description = std::move(description); }
|
void SetDescription(QString description) { m_description = std::move(description); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void enterEvent(QEnterEvent* event) override
|
void enterEvent(QEnterEvent* const event) override
|
||||||
{
|
{
|
||||||
if (m_timer_id)
|
// If the timer is already running, or the cursor is reentering the ToolTipWidget after having
|
||||||
return;
|
// hovered over the BalloonTip, don't start a new timer.
|
||||||
m_timer_id = this->startTimer(TOOLTIP_DELAY);
|
if (!m_timer_id && !BalloonTip::IsWidgetBalloonTipActive(*this))
|
||||||
|
m_timer_id = this->startTimer(TOOLTIP_DELAY);
|
||||||
|
|
||||||
|
Derived::enterEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void leaveEvent(QEvent* event) override { KillAndHide(); }
|
void leaveEvent(QEvent* const event) override
|
||||||
void hideEvent(QHideEvent* event) override { KillAndHide(); }
|
{
|
||||||
|
// 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);
|
this->killTimer(*m_timer_id);
|
||||||
m_timer_id.reset();
|
m_timer_id.reset();
|
||||||
|
|
||||||
BalloonTip::ShowBalloon(m_title, m_description,
|
BalloonTip::ShowBalloon(m_title, m_description,
|
||||||
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
|
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
|
||||||
|
|
||||||
|
Derived::timerEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual QPoint GetToolTipPosition() const = 0;
|
virtual QPoint GetToolTipPosition() const = 0;
|
||||||
|
|
||||||
void KillAndHide()
|
void KillTimerAndHideBalloon()
|
||||||
{
|
{
|
||||||
if (m_timer_id)
|
if (m_timer_id)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue