From 812186dc4c4d7447104ca43802de65ab4c69ea34 Mon Sep 17 00:00:00 2001 From: nerix Date: Sun, 3 Dec 2023 14:41:33 +0100 Subject: [PATCH 01/42] Return correct hit-test values for title bar buttons on Windows (#4994) Co-authored-by: Rasmus Karlsson --- CHANGELOG.md | 1 + src/CMakeLists.txt | 2 + src/widgets/BaseWindow.cpp | 172 ++++++++++++++++++++----- src/widgets/BaseWindow.hpp | 6 +- src/widgets/helper/TitlebarButton.cpp | 35 +++++ src/widgets/helper/TitlebarButton.hpp | 18 +++ src/widgets/helper/TitlebarButtons.cpp | 116 +++++++++++++++++ src/widgets/helper/TitlebarButtons.hpp | 69 ++++++++++ 8 files changed, 381 insertions(+), 38 deletions(-) create mode 100644 src/widgets/helper/TitlebarButtons.cpp create mode 100644 src/widgets/helper/TitlebarButtons.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 89bd46229..00c765cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 86c24c15b..1c6c3c0a0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/widgets/BaseWindow.cpp b/src/widgets/BaseWindow.cpp index a98ab8da8..59abe0a4f 100644 --- a/src/widgets/BaseWindow.cpp +++ b/src/widgets/BaseWindow.cpp @@ -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" @@ -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); @@ -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()) @@ -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: @@ -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); } @@ -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(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) @@ -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(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; } diff --git a/src/widgets/BaseWindow.hpp b/src/widgets/BaseWindow.hpp index 3b46aea6b..d55b5bd6d 100644 --- a/src/widgets/BaseWindow.hpp +++ b/src/widgets/BaseWindow.hpp @@ -18,6 +18,7 @@ namespace chatterino { class Button; class EffectLabel; class TitleBarButton; +class TitleBarButtons; enum class TitleBarButtonStyle; class BaseWindow : public BaseWidget @@ -135,9 +136,7 @@ private: 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