Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return correct hit-test values for title bar buttons on Windows #4994

Merged
merged 10 commits into from
Dec 3, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
- Bugfix: Fixed lookahead/-behind not working in _Ignores_. (#4965)
- Bugfix: Fixed Image Uploader accidentally deleting images with some hosts when link resolver was enabled. (#4971)
- Bugfix: Fixed rare crash with Image Uploader when closing a split right after starting an upload. (#4971)
- Bugfix: Fixed support for Windows 11 Snap layouts. (#4994)
- Bugfix: Fixed some windows appearing between screens. (#4797)
- Dev: Run miniaudio in a separate thread, and simplify it to not manage the device ourselves. There's a chance the simplification is a bad idea. (#4978)
- Dev: Change clang-format from v14 to v16. (#4929)
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,8 @@ set(SOURCE_FILES
widgets/helper/SignalLabel.hpp
widgets/helper/TitlebarButton.cpp
widgets/helper/TitlebarButton.hpp
widgets/helper/TitlebarButtons.cpp
widgets/helper/TitlebarButtons.hpp

widgets/listview/GenericItemDelegate.cpp
widgets/listview/GenericItemDelegate.hpp
Expand Down
172 changes: 137 additions & 35 deletions src/widgets/BaseWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "util/PostToThread.hpp"
#include "util/WindowsHelper.hpp"
#include "widgets/helper/EffectLabel.hpp"
#include "widgets/helper/TitlebarButtons.hpp"
#include "widgets/Label.hpp"
#include "widgets/TooltipWidget.hpp"
#include "widgets/Window.hpp"
Expand Down Expand Up @@ -180,9 +181,8 @@ void BaseWindow::init()
this->close();
});

this->ui_.minButton = _minButton;
this->ui_.maxButton = _maxButton;
this->ui_.exitButton = _exitButton;
this->ui_.titlebarButtons = new TitleBarButtons(
this, _minButton, _maxButton, _exitButton);

this->ui_.buttons.push_back(_minButton);
this->ui_.buttons.push_back(_maxButton);
Expand Down Expand Up @@ -474,12 +474,9 @@ void BaseWindow::changeEvent(QEvent *)
}

#ifdef USEWINSDK
if (this->ui_.maxButton)
if (this->ui_.titlebarButtons)
{
this->ui_.maxButton->setButtonStyle(
this->windowState() & Qt::WindowMaximized
? TitleBarButtonStyle::Unmaximize
: TitleBarButtonStyle::Maximize);
this->ui_.titlebarButtons->updateMaxButton();
}

if (this->isVisible() && this->hasCustomWindowFrame())
Expand Down Expand Up @@ -585,6 +582,11 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message,

bool returnValue = false;

auto isHoveringTitlebarButton = [&]() {
auto ht = msg->wParam;
return ht == HTMAXBUTTON || ht == HTMINBUTTON || ht == HTCLOSE;
};

switch (msg->message)
{
case WM_DPICHANGED:
Expand Down Expand Up @@ -612,6 +614,91 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message,
returnValue = this->handleNCHITTEST(msg, result);
break;

case WM_NCMOUSEHOVER:
case WM_NCMOUSEMOVE: {
// WM_NCMOUSEMOVE/WM_NCMOUSEHOVER gets sent when the mouse is
// moving/hovering in the non-client area
// - (mostly) the edges and the titlebar.
// We only need to handle the event for the titlebar buttons,
// as Qt doesn't create mouse events for these events.
if (!this->ui_.titlebarButtons)
{
// we don't consume the event if we don't have custom buttons
break;
}

if (isHoveringTitlebarButton())
{
*result = 0;
returnValue = true;
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);

RECT winrect;
GetWindowRect(HWND(winId()), &winrect);
QPoint globalPos(x, y);
this->ui_.titlebarButtons->hover(msg->wParam, globalPos);
this->lastEventWasNcMouseMove_ = true;
}
else
{
this->ui_.titlebarButtons->leave();
}
}
break;

case WM_MOUSEMOVE: {
if (!this->lastEventWasNcMouseMove_)
{
break;
}
this->lastEventWasNcMouseMove_ = false;
// Windows doesn't send WM_NCMOUSELEAVE in some cases,
// so the buttons show as hovered even though they're not hovered.
[[fallthrough]];
}
case WM_NCMOUSELEAVE: {
// WM_NCMOUSELEAVE gets sent when the mouse leaves any
// non-client area. In case we have titlebar buttons,
// we want to ensure they're deselected.
if (this->ui_.titlebarButtons)
{
this->ui_.titlebarButtons->leave();
}
}
break;

case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONUP: {
// WM_NCLBUTTON{DOWN, UP} gets called when the left mouse button
// was pressed in a non-client area.
// We simulate a mouse down/up event for the titlebar buttons
// as Qt doesn't create an event in that case.
if (!this->ui_.titlebarButtons || !isHoveringTitlebarButton())
{
break;
}
returnValue = true;
*result = 0;

auto ht = msg->wParam;
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);

RECT winrect;
GetWindowRect(HWND(winId()), &winrect);
QPoint globalPos(x, y);
if (msg->message == WM_NCLBUTTONDOWN)
{
this->ui_.titlebarButtons->mousePress(ht, globalPos);
}
else
{
this->ui_.titlebarButtons->mouseRelease(ht, globalPos);
}
}
break;

default:
return QWidget::nativeEvent(eventType, message, result);
}
Expand Down Expand Up @@ -668,29 +755,21 @@ void BaseWindow::calcButtonsSizes()
return;
}

if (this->frameless_)
if (this->frameless_ || !this->ui_.titlebarButtons)
{
return;
}

if ((this->width() / this->scale()) < 300)
#ifdef USEWINSDK
if ((static_cast<float>(this->width()) / this->scale()) < 300)
{
if (this->ui_.minButton)
this->ui_.minButton->setScaleIndependantSize(30, 30);
if (this->ui_.maxButton)
this->ui_.maxButton->setScaleIndependantSize(30, 30);
if (this->ui_.exitButton)
this->ui_.exitButton->setScaleIndependantSize(30, 30);
this->ui_.titlebarButtons->setSmallSize();
}
else
{
if (this->ui_.minButton)
this->ui_.minButton->setScaleIndependantSize(46, 30);
if (this->ui_.maxButton)
this->ui_.maxButton->setScaleIndependantSize(46, 30);
if (this->ui_.exitButton)
this->ui_.exitButton->setScaleIndependantSize(46, 30);
this->ui_.titlebarButtons->setRegularSize();
}
#endif
}

void BaseWindow::drawCustomWindowFrame(QPainter &painter)
Expand Down Expand Up @@ -943,32 +1022,55 @@ bool BaseWindow::handleNCHITTEST(MSG *msg, long *result)

if (*result == 0)
{
bool client = false;

// Check the main layout first, as it's the largest area
if (this->ui_.layoutBase->geometry().contains(point))
{
client = true;
*result = HTCLIENT;
}

// Check the titlebar buttons
if (!client && this->ui_.titlebarBox->geometry().contains(point))
if (*result == 0 &&
this->ui_.titlebarBox->geometry().contains(point))
{
for (QWidget *widget : this->ui_.buttons)
for (const auto *widget : this->ui_.buttons)
{
if (widget->isVisible() &&
widget->geometry().contains(point))
if (!widget->isVisible() ||
!widget->geometry().contains(point))
{
client = true;
continue;
}

if (const auto *btn =
dynamic_cast<const TitleBarButton *>(widget))
{
switch (btn->getButtonStyle())
{
case TitleBarButtonStyle::Minimize: {
*result = HTMINBUTTON;
break;
}
case TitleBarButtonStyle::Unmaximize:
case TitleBarButtonStyle::Maximize: {
*result = HTMAXBUTTON;
break;
}
case TitleBarButtonStyle::Close: {
*result = HTCLOSE;
break;
}
default: {
*result = HTCLIENT;
break;
}
}
break;
}
*result = HTCLIENT;
break;
}
}

if (client)
{
*result = HTCLIENT;
}
else
if (*result == 0)
{
*result = HTCAPTION;
}
Expand Down
6 changes: 3 additions & 3 deletions src/widgets/BaseWindow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace chatterino {
class Button;
class EffectLabel;
class TitleBarButton;
class TitleBarButtons;
enum class TitleBarButtonStyle;

class BaseWindow : public BaseWidget
Expand Down Expand Up @@ -135,9 +136,7 @@ class BaseWindow : public BaseWidget
QLayout *windowLayout = nullptr;
QHBoxLayout *titlebarBox = nullptr;
QWidget *titleLabel = nullptr;
TitleBarButton *minButton = nullptr;
TitleBarButton *maxButton = nullptr;
TitleBarButton *exitButton = nullptr;
TitleBarButtons *titlebarButtons = nullptr;
QWidget *layoutBase = nullptr;
std::vector<Button *> buttons;
} ui_;
Expand All @@ -148,6 +147,7 @@ class BaseWindow : public BaseWidget
QRect nextBounds_;
QTimer useNextBounds_;
bool isNotMinimizedOrMaximized_{};
bool lastEventWasNcMouseMove_ = false;
#endif

pajlada::Signals::SignalHolder connections_;
Expand Down
35 changes: 35 additions & 0 deletions src/widgets/helper/TitlebarButton.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,39 @@ void TitleBarButton::paintEvent(QPaintEvent *event)
this->paintButton(painter);
}

void TitleBarButton::ncEnter()
{
this->enterEvent(nullptr);
this->update();
}

void TitleBarButton::ncLeave()
{
this->leaveEvent(nullptr);
this->update();
}

void TitleBarButton::ncMove(QPoint at)
{
QMouseEvent evt(QMouseEvent::MouseMove, at, Qt::NoButton, Qt::NoButton,
Qt::NoModifier);
this->mouseMoveEvent(&evt);
}

void TitleBarButton::ncMousePress(QPoint at)
{
QMouseEvent evt(QMouseEvent::MouseButtonPress, at, Qt::LeftButton,
Qt::NoButton, Qt::NoModifier);
this->mousePressEvent(&evt);
this->update();
}

void TitleBarButton::ncMouseRelease(QPoint at)
{
QMouseEvent evt(QMouseEvent::MouseButtonRelease, at, Qt::LeftButton,
Qt::NoButton, Qt::NoModifier);
this->mouseReleaseEvent(&evt);
this->update();
}

} // namespace chatterino
18 changes: 18 additions & 0 deletions src/widgets/helper/TitlebarButton.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ class TitleBarButton : public Button
TitleBarButtonStyle getButtonStyle() const;
void setButtonStyle(TitleBarButtonStyle style_);

/// Simulate a `mouseEnter` event.
void ncEnter();

/// Simulate a `mouseLeave` event.
void ncLeave();

/// Simulate a `mouseMove` event.
/// @param at a local position relative to this widget
void ncMove(QPoint at);

/// Simulate a `mousePress` event with the left mouse button.
/// @param at a local position relative to this widget
void ncMousePress(QPoint at);

/// Simulate a `mouseRelease` event with the left mouse button.
/// @param at a local position relative to this widget
void ncMouseRelease(QPoint at);

protected:
void paintEvent(QPaintEvent *) override;

Expand Down
Loading
Loading