mirror of
https://github.com/thejerf/baijiSteamVR.git
synced 2026-10-17 16:02:33 +02:00
Fix the tooltips so they don't exceed the dialog's boundaries.
Steam OS intepreted that as a resize of the window and recentered it. This caused a lot of displeasing UI churn. This still leaves the little chevrons pointed at the wrong places sometimes but it is still a much better user experience.
This commit is contained in:
1 parent
8b4a0723fc
commit
14effdb34e
3 files changed
+112
-60
No files matched your search
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "DolphinQt/Config/ToolTipControls/BalloonTip.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
#include <QApplication>
|
||||
@@ -16,7 +18,6 @@
|
||||
#include <QPen>
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QScreen>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QVBoxLayout>
|
||||
@@ -39,7 +40,8 @@ QWidget* s_parent = nullptr;
|
||||
} // namespace
|
||||
|
||||
void BalloonTip::ShowBalloon(const QString& title, const QString& message,
|
||||
const QPoint& target_arrow_tip_position, QWidget* const parent,
|
||||
const QPoint& target_arrow_tip_position,
|
||||
const QRect& target_widget_rect_global, QWidget* const parent,
|
||||
const ShowArrow show_arrow, const int border_width)
|
||||
{
|
||||
HideBalloon();
|
||||
@@ -47,13 +49,16 @@ void BalloonTip::ShowBalloon(const QString& title, const QString& message,
|
||||
return;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
(void)target_widget_rect_global;
|
||||
QString the_message = message;
|
||||
the_message.replace(QStringLiteral("<dolphin_emphasis>"), QStringLiteral("<b>"));
|
||||
the_message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b>"));
|
||||
QToolTip::showText(target_arrow_tip_position, the_message, parent);
|
||||
#else
|
||||
s_the_balloon_tip = std::make_unique<BalloonTip>(PrivateTag{}, title, message, parent);
|
||||
s_the_balloon_tip->UpdateBoundsAndRedraw(target_arrow_tip_position, show_arrow, border_width);
|
||||
s_the_balloon_tip =
|
||||
std::make_unique<BalloonTip>(PrivateTag{}, title, message, parent, border_width);
|
||||
s_the_balloon_tip->UpdateBoundsAndRedraw(target_arrow_tip_position, target_widget_rect_global,
|
||||
show_arrow, border_width);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -70,12 +75,16 @@ void BalloonTip::HideBalloon()
|
||||
#endif
|
||||
}
|
||||
|
||||
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent)
|
||||
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent,
|
||||
const int border_width)
|
||||
: QWidget(nullptr, Qt::ToolTip)
|
||||
{
|
||||
s_parent = parent;
|
||||
setMouseTracking(true);
|
||||
|
||||
const QWidget* const owner_window = parent->window();
|
||||
m_owner_rect = QRect(owner_window->mapToGlobal(QPoint(0, 0)), owner_window->size());
|
||||
|
||||
QColor window_color;
|
||||
QColor text_color;
|
||||
QColor dolphin_emphasis;
|
||||
@@ -92,9 +101,19 @@ BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidge
|
||||
message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b></font>"));
|
||||
|
||||
auto* const balloontip_layout = new QVBoxLayout;
|
||||
balloontip_layout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
balloontip_layout->setSizeConstraint(QLayout::SetNoConstraint);
|
||||
setLayout(balloontip_layout);
|
||||
|
||||
const float border_half_width = border_width / 2.0;
|
||||
const int rounded_corner_margin = border_half_width / 4;
|
||||
const int horizontal_margin = border_width + rounded_corner_margin + 12;
|
||||
const int bounded_horizontal_margin =
|
||||
std::min(horizontal_margin, std::max(0, (m_owner_rect.width() - 1) / 2));
|
||||
const int max_width = std::max(1, m_owner_rect.width() - 2 * bounded_horizontal_margin);
|
||||
const int vertical_margin = bounded_horizontal_margin + 18;
|
||||
balloontip_layout->setContentsMargins(bounded_horizontal_margin, vertical_margin,
|
||||
bounded_horizontal_margin, vertical_margin);
|
||||
|
||||
const auto create_label = [=](const QString& text) {
|
||||
QLabel* const label = new QLabel;
|
||||
balloontip_layout->addWidget(label);
|
||||
@@ -102,7 +121,6 @@ BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidge
|
||||
label->setText(text);
|
||||
label->setTextFormat(Qt::RichText);
|
||||
|
||||
const int max_width = label->screen()->availableGeometry().width() / 3;
|
||||
label->setMaximumWidth(max_width);
|
||||
|
||||
if (label->sizeHint().width() > max_width)
|
||||
@@ -185,26 +203,25 @@ void BalloonTip::paintEvent(QPaintEvent* const event)
|
||||
}
|
||||
|
||||
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
const QRect& target_widget_rect_global,
|
||||
const ShowArrow show_arrow, const int border_full_width)
|
||||
{
|
||||
const float border_half_width = border_full_width / 2.0;
|
||||
|
||||
// This should be odd so that the arrow tip is a single pixel wide.
|
||||
const int arrow_full_width = 35;
|
||||
const float arrow_half_width = arrow_full_width / 2.0;
|
||||
// The y distance between the inner edge of the rectangle border and the inner tip of the arrow
|
||||
// border, and also the distance between the outer edge of the rectangle border and the outer tip
|
||||
// of the arrow border
|
||||
const int arrow_height = (1 + arrow_full_width) / 2;
|
||||
constexpr int preferred_arrow_full_width = 35;
|
||||
|
||||
// Distance between the label layout and the inner rectangle border edge
|
||||
const int balloon_interior_padding = 12;
|
||||
constexpr int balloon_interior_padding = 12;
|
||||
// Prevent the corners of the label layout from portruding into the rounded rectangle corners at
|
||||
// larger border sizes.
|
||||
const int rounded_corner_margin = border_half_width / 4;
|
||||
const int horizontal_margin =
|
||||
const int nominal_horizontal_margin =
|
||||
border_full_width + rounded_corner_margin + balloon_interior_padding;
|
||||
const int vertical_margin = horizontal_margin + arrow_height;
|
||||
const int horizontal_margin =
|
||||
std::min(nominal_horizontal_margin, std::max(0, (m_owner_rect.width() - 1) / 2));
|
||||
const int preferred_arrow_height = (1 + preferred_arrow_full_width) / 2;
|
||||
const int vertical_margin = horizontal_margin + preferred_arrow_height;
|
||||
|
||||
// Create enough space around the layout containing the title and message to draw the balloon and
|
||||
// both arrow tips (at most one of which will be visible)
|
||||
@@ -212,10 +229,45 @@ void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
vertical_margin);
|
||||
|
||||
QSize size_hint = sizeHint();
|
||||
size_hint.setWidth(std::min(size_hint.width(), m_owner_rect.width()));
|
||||
size_hint.setHeight(std::min(size_hint.height(), m_owner_rect.height()));
|
||||
|
||||
// Keep the balloon beside its target, choosing the side with more room if it must be clipped.
|
||||
const int space_above = std::max(0, target_widget_rect_global.top() - m_owner_rect.top());
|
||||
const int space_below =
|
||||
std::max(0, m_owner_rect.bottom() - target_widget_rect_global.bottom());
|
||||
const bool arrow_at_bottom =
|
||||
size_hint.height() <= space_above ||
|
||||
(size_hint.height() > space_below && space_above >= space_below);
|
||||
const int available_height = arrow_at_bottom ? space_above : space_below;
|
||||
size_hint.setHeight(std::min(size_hint.height(), std::max(1, available_height)));
|
||||
|
||||
// The content layout may prefer a larger size, but the visible tooltip is always bounded by its
|
||||
// owning window.
|
||||
setMaximumSize(m_owner_rect.size());
|
||||
resize(size_hint);
|
||||
|
||||
// Reduce the arrow with the available height/width so even unusually small windows have a
|
||||
// non-negative rounded-rectangle area.
|
||||
int arrow_full_width =
|
||||
show_arrow == ShowArrow::Yes ?
|
||||
std::min(preferred_arrow_full_width, size_hint.width() - border_full_width) :
|
||||
0;
|
||||
if (arrow_full_width > 0 && arrow_full_width % 2 == 0)
|
||||
--arrow_full_width;
|
||||
const int max_arrow_height = std::max(0, (size_hint.height() - border_full_width - 1) / 2);
|
||||
const int max_arrow_width = max_arrow_height > 0 ? 2 * max_arrow_height - 1 : 0;
|
||||
arrow_full_width = std::max(0, std::min(arrow_full_width, max_arrow_width));
|
||||
const float arrow_half_width = arrow_full_width / 2.0;
|
||||
// The y distance between the inner edge of the rectangle border and the inner tip of the arrow
|
||||
// border, and also the distance between the outer edge of the rectangle border and the outer tip
|
||||
// of the arrow border.
|
||||
const int arrow_height = (1 + arrow_full_width) / 2;
|
||||
|
||||
// These positions represent the middle of each edge of the BalloonTip's rounded rectangle
|
||||
const float rect_width = size_hint.width() - border_full_width;
|
||||
const float rect_height = size_hint.height() - border_full_width - 2 * arrow_height;
|
||||
const float rect_width = std::max(0, size_hint.width() - border_full_width);
|
||||
const float rect_height =
|
||||
std::max(0, size_hint.height() - border_full_width - 2 * arrow_height);
|
||||
const float rect_top = border_half_width + arrow_height;
|
||||
const float rect_bottom = rect_top + rect_height;
|
||||
const float rect_left = border_half_width;
|
||||
@@ -240,22 +292,29 @@ void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
// the left edge of the arrow interior; otherwise the distance between the right edges.
|
||||
const int arrow_nearest_edge_x_offset =
|
||||
std::min(adjusted_arrow_x_offset, centered_arrow_x_offset);
|
||||
const int arrow_tip_x_offset = arrow_nearest_edge_x_offset + arrow_half_width;
|
||||
|
||||
// The BalloonTip should be contained entirely within the screen that contains the target
|
||||
// position.
|
||||
QScreen* screen = QGuiApplication::screenAt(target_arrow_tip_position);
|
||||
if (screen == nullptr)
|
||||
{
|
||||
// If the target position isn't on any screen (which can happen if the window is partly off the
|
||||
// screen and the user hovers over the label) then use the screen containing the cursor instead.
|
||||
screen = QGuiApplication::screenAt(QCursor::pos());
|
||||
}
|
||||
const QRect screen_rect = screen->geometry();
|
||||
const int left_arrow_tip_x = static_cast<int>(arrow_nearest_edge_x_offset + arrow_half_width);
|
||||
const int right_arrow_tip_x =
|
||||
static_cast<int>(size_hint.width() - arrow_nearest_edge_x_offset - arrow_full_width +
|
||||
arrow_half_width);
|
||||
const int rightmost_valid_x =
|
||||
std::max(m_owner_rect.left(), m_owner_rect.right() - size_hint.width() + 1);
|
||||
const int left_arrow_window_x = std::clamp(target_arrow_tip_position.x() - left_arrow_tip_x,
|
||||
m_owner_rect.left(), rightmost_valid_x);
|
||||
const int right_arrow_window_x = std::clamp(target_arrow_tip_position.x() - right_arrow_tip_x,
|
||||
m_owner_rect.left(), rightmost_valid_x);
|
||||
const int left_arrow_error =
|
||||
std::abs(left_arrow_window_x + left_arrow_tip_x - target_arrow_tip_position.x());
|
||||
const int right_arrow_error =
|
||||
std::abs(right_arrow_window_x + right_arrow_tip_x - target_arrow_tip_position.x());
|
||||
const bool arrow_at_left = left_arrow_error <= right_arrow_error;
|
||||
const int actual_balloontip_global_x = arrow_at_left ? left_arrow_window_x : right_arrow_window_x;
|
||||
|
||||
QPainterPath rect_path;
|
||||
rect_path.addRoundedRect(rect_left, rect_top, rect_width, rect_height, corner_outer_radius,
|
||||
corner_outer_radius);
|
||||
if (rect_width > 0 && rect_height > 0)
|
||||
{
|
||||
rect_path.addRoundedRect(rect_left, rect_top, rect_width, rect_height, corner_outer_radius,
|
||||
corner_outer_radius);
|
||||
}
|
||||
|
||||
// The default pen cap style Qt::SquareCap extends drawn lines one half width before the starting
|
||||
// point and one half width after the ending point such that the starting and ending points are
|
||||
@@ -290,25 +349,18 @@ void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
mask_painter.setBrush(QBrush(Qt::color1));
|
||||
mask_painter.drawPath(rect_path);
|
||||
|
||||
const bool arrow_at_bottom =
|
||||
target_arrow_tip_position.y() - size_hint.height() + arrow_height >= 0;
|
||||
const bool arrow_at_left =
|
||||
target_arrow_tip_position.x() + size_hint.width() - arrow_tip_x_offset < screen_rect.width();
|
||||
|
||||
const float arrow_base_y =
|
||||
arrow_at_bottom ? rect_bottom - border_half_width : rect_top + border_half_width;
|
||||
|
||||
const float arrow_tip_vertical_offset = arrow_at_bottom ? arrow_height : -arrow_height;
|
||||
const float arrow_tip_interior_y = arrow_base_y + arrow_tip_vertical_offset;
|
||||
const float arrow_tip_exterior_y =
|
||||
arrow_tip_interior_y + (arrow_at_bottom ? border_full_width : -border_full_width);
|
||||
const float arrow_base_left_edge_x =
|
||||
arrow_at_left ? arrow_nearest_edge_x_offset :
|
||||
size_hint.width() - arrow_nearest_edge_x_offset - arrow_full_width;
|
||||
const float arrow_base_right_edge_x = arrow_base_left_edge_x + arrow_full_width;
|
||||
const float arrow_tip_x = arrow_base_left_edge_x + arrow_half_width;
|
||||
|
||||
if (show_arrow == ShowArrow::Yes)
|
||||
if (show_arrow == ShowArrow::Yes && arrow_full_width > 0)
|
||||
{
|
||||
// Drawing diagonal lines in Qt is filled with edge cases and inexplicable behavior. Getting it
|
||||
// to do what you want at one border size is simple enough, but doing so flexibly is an exercise
|
||||
@@ -377,22 +429,15 @@ void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
|
||||
setMask(mask_bitmap);
|
||||
|
||||
// Place the arrow tip at the target position whether the arrow tip is drawn or not
|
||||
const int target_balloontip_global_x =
|
||||
target_arrow_tip_position.x() - static_cast<int>(arrow_tip_x);
|
||||
const int rightmost_valid_balloontip_global_x =
|
||||
screen_rect.left() + screen_rect.width() - size_hint.width();
|
||||
// If the balloon would extend off the screen, push it left or right until it's not
|
||||
const int actual_balloontip_global_x =
|
||||
std::max(screen_rect.left(),
|
||||
std::min(rightmost_valid_balloontip_global_x, target_balloontip_global_x));
|
||||
// The tip pixel should be in the middle of the control, and arrow_tip_exterior_y is at the bottom
|
||||
// of that pixel. When arrow_at_bottom is true the arrow is above arrow_tip_exterior_y and so the
|
||||
// tip pixel is in the right place, but when it's false the arrow is below arrow_tip_exterior_y
|
||||
// so the tip pixel would be the one below that. Make this adjustment to fix that.
|
||||
const int tip_pixel_adjustment = arrow_at_bottom ? 0 : 1;
|
||||
// Anchor the popup edge to the source widget edge selected for this side, so its border doesn't
|
||||
// cut through the widget.
|
||||
const int preferred_balloontip_global_y =
|
||||
arrow_at_bottom ? target_widget_rect_global.top() - size_hint.height() :
|
||||
target_widget_rect_global.bottom() + 1;
|
||||
const int lowest_valid_y =
|
||||
std::max(m_owner_rect.top(), m_owner_rect.bottom() - size_hint.height() + 1);
|
||||
const int actual_balloontip_global_y =
|
||||
target_arrow_tip_position.y() - arrow_tip_exterior_y - tip_pixel_adjustment;
|
||||
std::clamp(preferred_balloontip_global_y, m_owner_rect.top(), lowest_valid_y);
|
||||
|
||||
move(actual_balloontip_global_x, actual_balloontip_global_y);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <QColor>
|
||||
#include <QPixmap>
|
||||
#include <QRect>
|
||||
#include <QWidget>
|
||||
|
||||
class QEnterEvent;
|
||||
@@ -29,14 +30,15 @@ public:
|
||||
No
|
||||
};
|
||||
static void ShowBalloon(const QString& title, const QString& message,
|
||||
const QPoint& target_arrow_tip_position, QWidget* parent,
|
||||
const QPoint& target_arrow_tip_position,
|
||||
const QRect& target_widget_rect_global, 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);
|
||||
BalloonTip(PrivateTag, const QString& title, QString message, QWidget* parent, int border_width);
|
||||
|
||||
protected:
|
||||
void enterEvent(QEnterEvent* event) override;
|
||||
@@ -45,9 +47,11 @@ protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow,
|
||||
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
const QRect& target_widget_rect_global, ShowArrow show_arrow,
|
||||
int border_width);
|
||||
|
||||
QColor m_border_color;
|
||||
QPixmap m_pixmap;
|
||||
QRect m_owner_rect;
|
||||
};
|
||||
@@ -60,8 +60,11 @@ private:
|
||||
this->killTimer(*m_timer_id);
|
||||
m_timer_id.reset();
|
||||
|
||||
BalloonTip::ShowBalloon(m_title, m_description,
|
||||
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
|
||||
const QPoint target_position = this->parentWidget()->mapToGlobal(GetToolTipPosition());
|
||||
const QRect target_widget_rect_global(this->mapToGlobal(this->rect().topLeft()),
|
||||
this->rect().size());
|
||||
BalloonTip::ShowBalloon(m_title, m_description, target_position, target_widget_rect_global,
|
||||
this);
|
||||
|
||||
Derived::timerEvent(event);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user