Allow any window to be bounds-checked (#4802)

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
nerix 2023-09-24 15:32:43 +02:00 committed by GitHub
parent 37009e8e6b
commit 783c7530f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 122 additions and 88 deletions

View file

@ -458,6 +458,8 @@ set(SOURCE_FILES
util/Twitch.cpp
util/Twitch.hpp
util/TypeName.hpp
util/WidgetHelpers.cpp
util/WidgetHelpers.hpp
util/WindowsHelper.cpp
util/WindowsHelper.hpp
util/XDGDesktopFile.cpp

View file

@ -926,7 +926,7 @@ void CommandController::initialize(Settings &, Paths &paths)
currentSplit);
userPopup->setData(userName, channel);
userPopup->moveTo(QCursor::pos(),
BaseWindow::BoundsChecker::CursorPosition);
widgets::BoundsChecking::CursorPosition);
userPopup->show();
return "";
});

View file

@ -0,0 +1,82 @@
#include "util/WidgetHelpers.hpp"
#include <QCursor>
#include <QGuiApplication>
#include <QPoint>
#include <QScreen>
#include <QWidget>
namespace {
/// Move the `window` into the `screen` geometry if it's not already in there.
void moveWithinScreen(QWidget *window, QScreen *screen, QPoint point)
{
if (screen == nullptr)
{
screen = QGuiApplication::primaryScreen();
}
const QRect bounds = screen->availableGeometry();
bool stickRight = false;
bool stickBottom = false;
const auto w = window->frameGeometry().width();
const auto h = window->frameGeometry().height();
if (point.x() < bounds.left())
{
point.setX(bounds.left());
}
if (point.y() < bounds.top())
{
point.setY(bounds.top());
}
if (point.x() + w > bounds.right())
{
stickRight = true;
point.setX(bounds.right() - w);
}
if (point.y() + h > bounds.bottom())
{
stickBottom = true;
point.setY(bounds.bottom() - h);
}
if (stickRight && stickBottom)
{
const QPoint globalCursorPos = QCursor::pos();
point.setY(globalCursorPos.y() - window->height() - 16);
}
window->move(point);
}
} // namespace
namespace chatterino::widgets {
void moveWindowTo(QWidget *window, QPoint position, BoundsChecking mode)
{
switch (mode)
{
case BoundsChecking::Off: {
window->move(position);
}
break;
case BoundsChecking::CursorPosition: {
moveWithinScreen(window, QGuiApplication::screenAt(QCursor::pos()),
position);
}
break;
case BoundsChecking::DesiredPosition: {
moveWithinScreen(window, QGuiApplication::screenAt(position),
position);
}
break;
}
}
} // namespace chatterino::widgets

View file

@ -0,0 +1,29 @@
#pragma once
class QWidget;
class QPoint;
class QScreen;
namespace chatterino::widgets {
enum class BoundsChecking {
/// Don't do any bounds checking (equivalent to `QWidget::move`).
Off,
/// Attempt to keep the window within bounds of the screen the cursor is on.
CursorPosition,
/// Attempt to keep the window within bounds of the screen the desired position is on.
DesiredPosition,
};
/// Moves the `window` to the (global) `position`
/// while doing bounds-checking according to `mode` to ensure the window stays on one screen.
///
/// @param window The window to move.
/// @param position The global position to move the window to.
/// @param mode The desired bounds checking.
void moveWindowTo(QWidget *window, QPoint position,
BoundsChecking mode = BoundsChecking::DesiredPosition);
} // namespace chatterino::widgets

View file

@ -503,28 +503,9 @@ void BaseWindow::leaveEvent(QEvent *)
TooltipWidget::instance()->hide();
}
void BaseWindow::moveTo(QPoint point, BoundsChecker boundsChecker)
void BaseWindow::moveTo(QPoint point, widgets::BoundsChecking mode)
{
switch (boundsChecker)
{
case BoundsChecker::Off: {
// The bounds checker is off, *just* move the window
this->move(point);
}
break;
case BoundsChecker::CursorPosition: {
// The bounds checker is on, use the cursor position as the origin
this->moveWithinScreen(point, QCursor::pos());
}
break;
case BoundsChecker::DesiredPosition: {
// The bounds checker is on, use the desired position as the origin
this->moveWithinScreen(point, point);
}
break;
}
widgets::moveWindowTo(this, point, mode);
}
void BaseWindow::resizeEvent(QResizeEvent *)
@ -580,51 +561,6 @@ void BaseWindow::showEvent(QShowEvent *)
{
}
void BaseWindow::moveWithinScreen(QPoint point, QPoint origin)
{
// move the widget into the screen geometry if it's not already in there
auto *screen = QApplication::screenAt(origin);
if (screen == nullptr)
{
screen = QApplication::primaryScreen();
}
const QRect bounds = screen->availableGeometry();
bool stickRight = false;
bool stickBottom = false;
const auto w = this->frameGeometry().width();
const auto h = this->frameGeometry().height();
if (point.x() < bounds.left())
{
point.setX(bounds.left());
}
if (point.y() < bounds.top())
{
point.setY(bounds.top());
}
if (point.x() + w > bounds.right())
{
stickRight = true;
point.setX(bounds.right() - w);
}
if (point.y() + h > bounds.bottom())
{
stickBottom = true;
point.setY(bounds.bottom() - h);
}
if (stickRight && stickBottom)
{
const QPoint globalCursorPos = QCursor::pos();
point.setY(globalCursorPos.y() - this->height() - 16);
}
this->move(point);
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message,
qintptr *result)

View file

@ -1,6 +1,7 @@
#pragma once
#include "common/FlagsEnum.hpp"
#include "util/WidgetHelpers.hpp"
#include "widgets/BaseWidget.hpp"
#include <pajlada/signals/signalholder.hpp>
@ -36,17 +37,6 @@ public:
DisableLayoutSave = 128,
};
enum class BoundsChecker {
// Don't attempt to do any "stay in screen" stuff, just move me!
Off,
// Attempt to keep the window within bounds of the screen the cursor is on
CursorPosition,
// Attempt to keep the window within bounds of the screen the desired position is on
DesiredPosition,
};
enum ActionOnFocusLoss { Nothing, Delete, Close, Hide };
explicit BaseWindow(FlagsEnum<Flags> flags_ = None,
@ -65,7 +55,7 @@ public:
void setActionOnFocusLoss(ActionOnFocusLoss value);
ActionOnFocusLoss getActionOnFocusLoss() const;
void moveTo(QPoint point, BoundsChecker boundsChecker);
void moveTo(QPoint point, widgets::BoundsChecking mode);
float scale() const override;
float qtFontScale() const;
@ -110,11 +100,6 @@ protected:
private:
void init();
/**
*
**/
void moveWithinScreen(QPoint point, QPoint origin);
void calcButtonsSizes();
void drawCustomWindowFrame(QPainter &painter);
void onFocusLost();

View file

@ -207,7 +207,7 @@ EmotePopup::EmotePopup(QWidget *parent)
{
// this->setStayInScreenRect(true);
this->moveTo(getApp()->windows->emotePopupPos(),
BaseWindow::BoundsChecker::DesiredPosition);
widgets::BoundsChecking::DesiredPosition);
auto *layout = new QVBoxLayout();
this->getLayoutContainer()->setLayout(layout);

View file

@ -1832,7 +1832,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
}
tooltipWidget->moveTo(event->globalPos() + QPoint(16, 16),
BaseWindow::BoundsChecker::CursorPosition);
widgets::BoundsChecking::CursorPosition);
tooltipWidget->setWordWrap(isLinkValid);
tooltipWidget->show();
}
@ -2687,7 +2687,7 @@ void ChannelView::showUserInfoPopup(const QString &userName,
QPoint offset(userPopup->width() / 3, userPopup->height() / 5);
userPopup->moveTo(QCursor::pos() - offset,
BaseWindow::BoundsChecker::CursorPosition);
widgets::BoundsChecking::CursorPosition);
userPopup->show();
}

View file

@ -956,7 +956,7 @@ void SplitHeader::enterEvent(QEvent *event)
auto pos = this->mapToGlobal(this->rect().bottomLeft()) +
QPoint((this->width() - tooltip->width()) / 2, 1);
tooltip->moveTo(pos, BaseWindow::BoundsChecker::CursorPosition);
tooltip->moveTo(pos, widgets::BoundsChecking::CursorPosition);
tooltip->show();
}