Skip to content

Commit

Permalink
Allow any window to be bounds-checked (Chatterino#4802)
Browse files Browse the repository at this point in the history
Co-authored-by: Rasmus Karlsson <[email protected]>
  • Loading branch information
Nerixyz and pajlada authored Sep 24, 2023
1 parent 37009e8 commit 783c753
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 88 deletions.
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/commands/CommandController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 "";
});
Expand Down
82 changes: 82 additions & 0 deletions src/util/WidgetHelpers.cpp
Original file line number Diff line number Diff line change
@@ -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
29 changes: 29 additions & 0 deletions src/util/WidgetHelpers.hpp
Original file line number Diff line number Diff line change
@@ -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
68 changes: 2 additions & 66 deletions src/widgets/BaseWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 *)
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 2 additions & 17 deletions src/widgets/BaseWindow.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "common/FlagsEnum.hpp"
#include "util/WidgetHelpers.hpp"
#include "widgets/BaseWidget.hpp"

#include <pajlada/signals/signalholder.hpp>
Expand Down Expand Up @@ -36,17 +37,6 @@ class BaseWindow : public BaseWidget
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,
Expand All @@ -65,7 +55,7 @@ class BaseWindow : public BaseWidget
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;
Expand Down Expand Up @@ -110,11 +100,6 @@ class BaseWindow : public BaseWidget
private:
void init();

/**
*
**/
void moveWithinScreen(QPoint point, QPoint origin);

void calcButtonsSizes();
void drawCustomWindowFrame(QPainter &painter);
void onFocusLost();
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/dialogs/EmotePopup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/widgets/helper/ChannelView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
}

Expand Down
2 changes: 1 addition & 1 deletion src/widgets/splits/SplitHeader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down

0 comments on commit 783c753

Please sign in to comment.