diff --git a/CHANGELOG.md b/CHANGELOG.md index b74cadd2656..360c79c0177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ - Bugfix: Fixed split header tooltips appearing too tall. (#5232) - Bugfix: Fixed past messages not showing in the search popup after adding a channel. (#5248) - Bugfix: Detect when OBS is running on MacOS. (#5260) +- Bugfix: Fixed broken scaling in context menus. (#4868) - 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) - Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791) diff --git a/resources/qss/settings.qss b/resources/qss/settings.qss index 6d5114423b0..93c69b603ed 100644 --- a/resources/qss/settings.qss +++ b/resources/qss/settings.qss @@ -1,11 +1,11 @@ * { - font-size: px; + font-size: 14px; font-family: "Segoe UI"; } QCheckBox::indicator { - width: px; - height: px; + width: 14px; + height: 14px; } chatterino--ComboBox { diff --git a/src/RunGui.cpp b/src/RunGui.cpp index 13012957dc2..4e6fabc3bfe 100644 --- a/src/RunGui.cpp +++ b/src/RunGui.cpp @@ -77,19 +77,7 @@ namespace { void initQt() { // set up the QApplication flags - QApplication::setAttribute(Qt::AA_Use96Dpi, true); - -#ifdef Q_OS_WIN32 - // Avoid promoting child widgets to child windows - // This causes bugs with frameless windows as not all child events - // get sent to the parent - effectively making the window immovable. - QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); -#endif - -#if defined(Q_OS_WIN32) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true); -#endif - + QApplication::setAttribute(Qt::AA_Use96Dpi); QApplication::setStyle(QStyleFactory::create("Fusion")); #ifndef Q_OS_MAC diff --git a/src/main.cpp b/src/main.cpp index ef59af0c529..8da92a45ca6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,11 +26,6 @@ using namespace chatterino; int main(int argc, char **argv) { - // TODO: This is a temporary fix (see #4552). -#if defined(Q_OS_WINDOWS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - qputenv("QT_ENABLE_HIGHDPI_SCALING", "0"); -#endif - QApplication a(argc, argv); QCoreApplication::setApplicationName("chatterino"); diff --git a/src/messages/MessageElement.cpp b/src/messages/MessageElement.cpp index 56d1e2ed3d3..114d029392d 100644 --- a/src/messages/MessageElement.cpp +++ b/src/messages/MessageElement.cpp @@ -155,8 +155,8 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container, { if (flags.has(MessageElementFlag::EmoteImages)) { - auto image = - this->emote_->images.getImageOrLoaded(container.getScale()); + auto image = this->emote_->images.getImageOrLoaded( + container.getImageScale()); if (image->isEmpty()) { return; @@ -210,7 +210,7 @@ void LayeredEmoteElement::addToContainer(MessageLayoutContainer &container, { if (flags.has(MessageElementFlag::EmoteImages)) { - auto images = this->getLoadedImages(container.getScale()); + auto images = this->getLoadedImages(container.getImageScale()); if (images.empty()) { return; @@ -364,7 +364,7 @@ void BadgeElement::addToContainer(MessageLayoutContainer &container, if (flags.hasAny(this->getFlags())) { auto image = - this->emote_->images.getImageOrLoaded(container.getScale()); + this->emote_->images.getImageOrLoaded(container.getImageScale()); if (image->isEmpty()) { return; @@ -797,7 +797,7 @@ void ScalingImageElement::addToContainer(MessageLayoutContainer &container, if (flags.hasAny(this->getFlags())) { const auto &image = - this->images_.getImageOrLoaded(container.getScale()); + this->images_.getImageOrLoaded(container.getImageScale()); if (image->isEmpty()) { return; diff --git a/src/messages/layouts/MessageLayout.cpp b/src/messages/layouts/MessageLayout.cpp index 126bb40c94d..8a20b05cc19 100644 --- a/src/messages/layouts/MessageLayout.cpp +++ b/src/messages/layouts/MessageLayout.cpp @@ -74,7 +74,8 @@ int MessageLayout::getWidth() const // Layout // return true if redraw is required -bool MessageLayout::layout(int width, float scale, MessageElementFlags flags, +bool MessageLayout::layout(int width, float scale, float imageScale, + MessageElementFlags flags, bool shouldInvalidateBuffer) { // BenchmarkGuard benchmark("MessageLayout::layout()"); @@ -106,6 +107,8 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags, // check if dpi changed layoutRequired |= this->scale_ != scale; this->scale_ = scale; + layoutRequired |= this->imageScale_ != imageScale; + this->imageScale_ = imageScale; if (!layoutRequired) { @@ -148,7 +151,8 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) bool hideSimilar = getSettings()->hideSimilar; bool hideReplies = !flags.has(MessageElementFlag::RepliedMessage); - this->container_.beginLayout(width, this->scale_, messageFlags); + this->container_.beginLayout(width, this->scale_, this->imageScale_, + messageFlags); for (const auto &element : this->message_->elements) { @@ -288,16 +292,11 @@ QPixmap *MessageLayout::ensureBuffer(QPainter &painter, int width) } // Create new buffer -#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) this->buffer_ = std::make_unique( int(width * painter.device()->devicePixelRatioF()), int(this->container_.getHeight() * painter.device()->devicePixelRatioF())); this->buffer_->setDevicePixelRatio(painter.device()->devicePixelRatioF()); -#else - this->buffer_ = std::make_unique( - width, std::max(16, this->container_.getHeight())); -#endif this->bufferValid_ = false; DebugCount::increase("message drawing buffers"); diff --git a/src/messages/layouts/MessageLayout.hpp b/src/messages/layouts/MessageLayout.hpp index f54f57d2a9a..01958ddf2ab 100644 --- a/src/messages/layouts/MessageLayout.hpp +++ b/src/messages/layouts/MessageLayout.hpp @@ -56,8 +56,8 @@ class MessageLayout MessageLayoutFlags flags; - bool layout(int width, float scale_, MessageElementFlags flags, - bool shouldInvalidateBuffer); + bool layout(int width, float scale_, float imageScale, + MessageElementFlags flags, bool shouldInvalidateBuffer); // Painting MessagePaintResult paint(const MessagePaintContext &ctx); @@ -128,6 +128,7 @@ class MessageLayout int currentLayoutWidth_ = -1; int layoutState_ = -1; float scale_ = -1; + float imageScale_ = -1.F; MessageElementFlags currentWordFlags_; #ifdef FOURTF diff --git a/src/messages/layouts/MessageLayoutContainer.cpp b/src/messages/layouts/MessageLayoutContainer.cpp index 29d70e0a193..15a7d71afc3 100644 --- a/src/messages/layouts/MessageLayoutContainer.cpp +++ b/src/messages/layouts/MessageLayoutContainer.cpp @@ -30,7 +30,7 @@ constexpr const QMargins MARGIN{8, 4, 8, 4}; namespace chatterino { void MessageLayoutContainer::beginLayout(int width, float scale, - MessageFlags flags) + float imageScale, MessageFlags flags) { this->elements_.clear(); this->lines_.clear(); @@ -45,6 +45,7 @@ void MessageLayoutContainer::beginLayout(int width, float scale, this->width_ = width; this->height_ = 0; this->scale_ = scale; + this->imageScale_ = imageScale; this->flags_ = flags; auto mediumFontMetrics = getIApp()->getFonts()->getFontMetrics(FontStyle::ChatMedium, scale); @@ -526,6 +527,11 @@ float MessageLayoutContainer::getScale() const return this->scale_; } +float MessageLayoutContainer::getImageScale() const +{ + return this->imageScale_; +} + bool MessageLayoutContainer::isCollapsed() const { return this->isCollapsed_; diff --git a/src/messages/layouts/MessageLayoutContainer.hpp b/src/messages/layouts/MessageLayoutContainer.hpp index ed3c1a7a6b7..dde3f4d452f 100644 --- a/src/messages/layouts/MessageLayoutContainer.hpp +++ b/src/messages/layouts/MessageLayoutContainer.hpp @@ -32,7 +32,8 @@ struct MessageLayoutContainer { * This will reset all line calculations, and will be considered incomplete * until the accompanying end function has been called */ - void beginLayout(int width_, float scale_, MessageFlags flags_); + void beginLayout(int width, float scale, float imageScale, + MessageFlags flags); /** * Finish the layout process of this message @@ -146,6 +147,11 @@ struct MessageLayoutContainer { */ float getScale() const; + /** + * Returns the image scale + */ + float getImageScale() const; + /** * Returns true if this message is collapsed */ @@ -270,6 +276,10 @@ struct MessageLayoutContainer { // variables float scale_ = 1.F; + /** + * Scale factor for images + */ + float imageScale_ = 1.F; int width_ = 0; MessageFlags flags_{}; /** diff --git a/src/widgets/AttachedWindow.cpp b/src/widgets/AttachedWindow.cpp index 5ce232a1f2f..b83afb65db5 100644 --- a/src/widgets/AttachedWindow.cpp +++ b/src/widgets/AttachedWindow.cpp @@ -270,20 +270,22 @@ void AttachedWindow::updateWindowRect(void *_attachedPtr) } float scale = 1.f; + float ourScale = 1.F; if (auto dpi = getWindowDpi(attached)) { scale = *dpi / 96.f; + ourScale = scale / this->devicePixelRatio(); for (auto w : this->ui_.split->findChildren()) { - w->setOverrideScale(scale); + w->setOverrideScale(ourScale); } - this->ui_.split->setOverrideScale(scale); + this->ui_.split->setOverrideScale(ourScale); } if (this->height_ != -1) { - this->ui_.split->setFixedWidth(int(this->width_ * scale)); + this->ui_.split->setFixedWidth(int(this->width_ * ourScale)); // offset int o = this->fullscreen_ ? 0 : 8; diff --git a/src/widgets/BaseWindow.cpp b/src/widgets/BaseWindow.cpp index c819c8f4a88..1a8f5fb6a73 100644 --- a/src/widgets/BaseWindow.cpp +++ b/src/widgets/BaseWindow.cpp @@ -29,12 +29,157 @@ # pragma comment(lib, "Dwmapi.lib") # include - -# define WM_DPICHANGED 0x02E0 +# include #endif #include "widgets/helper/TitlebarButton.hpp" +namespace { + +#ifdef USEWINSDK + +bool isWindows11OrGreater() +{ + static const auto result = [&] { + if (!::IsWindows10OrGreater()) + { + return false; + } + auto version = QOperatingSystemVersion::current(); + return (version.majorVersion() > 10) || + (version.microVersion() >= 22000); + }(); + + return result; +} + +/// Finds the taskbar HWND on a specific monitor (or any) +HWND findTaskbarWindow(LPRECT rcMon = nullptr) +{ + HWND taskbar = nullptr; + RECT taskbarRect; + // return value of IntersectRect, unused + RECT intersectionRect; + + while ((taskbar = FindWindowEx(nullptr, taskbar, L"Shell_TrayWnd", + nullptr)) != nullptr) + { + if (!rcMon) + { + // no monitor was specified, return the first encountered window + break; + } + if (GetWindowRect(taskbar, &taskbarRect) != 0 && + IntersectRect(&intersectionRect, &taskbarRect, rcMon) != 0) + { + // taskbar intersects with the monitor - this is the one + break; + } + } + + return taskbar; +} + +/// Gets the edge of the taskbar if it's automatically hidden +std::optional taskbarEdge(LPRECT rcMon = nullptr) +{ + HWND taskbar = findTaskbarWindow(rcMon); + if (!taskbar) + { + return std::nullopt; + } + + APPBARDATA state = {sizeof(state), taskbar}; + APPBARDATA pos = {sizeof(pos), taskbar}; + + auto appBarState = + static_cast(SHAppBarMessage(ABM_GETSTATE, &state)); + if ((appBarState & ABS_AUTOHIDE) == 0) + { + return std::nullopt; + } + + if (SHAppBarMessage(ABM_GETTASKBARPOS, &pos) == 0) + { + qCDebug(chatterinoApp) << "Failed to get taskbar pos"; + return ABE_BOTTOM; + } + + return pos.uEdge; +} + +/// Gets the window borders for @a hwnd +RECT windowBordersFor(HWND hwnd, bool isMaximized) +{ + RECT margins{0, 0, 0, 0}; + + auto addBorders = isMaximized || isWindows11OrGreater(); + if (addBorders) + { + auto dpi = GetDpiForWindow(hwnd); + LONG borderWidth{}; + if (dpi != 0) + { + borderWidth = GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); + } + else + { + borderWidth = GetSystemMetrics(SM_CXSIZEFRAME) + + GetSystemMetrics(SM_CXPADDEDBORDER); + } + margins.left += borderWidth; + margins.right -= borderWidth; + if (isMaximized) + { + margins.top += borderWidth; + } + margins.bottom -= borderWidth; + } + + if (isMaximized) + { + auto *hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi; + mi.cbSize = sizeof(mi); + auto *monitor = [&]() -> LPRECT { + if (GetMonitorInfo(hMonitor, &mi)) + { + return &mi.rcMonitor; + } + return nullptr; + }(); + + auto edge = taskbarEdge(monitor); + if (edge) + { + switch (*edge) + { + case ABE_LEFT: + margins.left += 1; + break; + case ABE_RIGHT: + margins.right -= 1; + break; + case ABE_TOP: + margins.top += 1; + break; + case ABE_BOTTOM: + margins.bottom -= 1; + break; + default: + break; + } + } + } + + return margins; +} + +#endif + +} // namespace + namespace chatterino { BaseWindow::BaseWindow(FlagsEnum _flags, QWidget *parent) @@ -128,84 +273,74 @@ void BaseWindow::init() if (this->hasCustomWindowFrame()) { // CUSTOM WINDOW FRAME - QVBoxLayout *layout = new QVBoxLayout(); + auto *layout = new QVBoxLayout(this); this->ui_.windowLayout = layout; - layout->setContentsMargins(1, 1, 1, 1); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); - this->setLayout(layout); + + if (!this->frameless_) { - if (!this->frameless_) - { - QHBoxLayout *buttonLayout = this->ui_.titlebarBox = - new QHBoxLayout(); - buttonLayout->setContentsMargins(0, 0, 0, 0); - layout->addLayout(buttonLayout); - - // title - Label *title = new Label; - QObject::connect(this, &QWidget::windowTitleChanged, - [title](const QString &text) { - title->setText(text); - }); - - QSizePolicy policy(QSizePolicy::Ignored, - QSizePolicy::Preferred); - policy.setHorizontalStretch(1); - title->setSizePolicy(policy); - buttonLayout->addWidget(title); - this->ui_.titleLabel = title; - - // buttons - TitleBarButton *_minButton = new TitleBarButton; - _minButton->setButtonStyle(TitleBarButtonStyle::Minimize); - TitleBarButton *_maxButton = new TitleBarButton; - _maxButton->setButtonStyle(TitleBarButtonStyle::Maximize); - TitleBarButton *_exitButton = new TitleBarButton; - _exitButton->setButtonStyle(TitleBarButtonStyle::Close); - - QObject::connect(_minButton, &TitleBarButton::leftClicked, this, - [this] { - this->setWindowState(Qt::WindowMinimized | - this->windowState()); - }); - QObject::connect(_maxButton, &TitleBarButton::leftClicked, this, - [this, _maxButton] { - this->setWindowState( - _maxButton->getButtonStyle() != + QHBoxLayout *buttonLayout = this->ui_.titlebarBox = + new QHBoxLayout(); + buttonLayout->setContentsMargins(0, 0, 0, 0); + layout->addLayout(buttonLayout); + + // title + Label *title = new Label; + QObject::connect(this, &QWidget::windowTitleChanged, + [title](const QString &text) { + title->setText(text); + }); + + QSizePolicy policy(QSizePolicy::Ignored, QSizePolicy::Preferred); + policy.setHorizontalStretch(1); + title->setSizePolicy(policy); + buttonLayout->addWidget(title); + this->ui_.titleLabel = title; + + // buttons + auto *minButton = new TitleBarButton; + minButton->setButtonStyle(TitleBarButtonStyle::Minimize); + auto *maxButton = new TitleBarButton; + maxButton->setButtonStyle(TitleBarButtonStyle::Maximize); + auto *exitButton = new TitleBarButton; + exitButton->setButtonStyle(TitleBarButtonStyle::Close); + + QObject::connect(minButton, &TitleBarButton::leftClicked, this, + [this] { + this->setWindowState(Qt::WindowMinimized | + this->windowState()); + }); + QObject::connect( + maxButton, &TitleBarButton::leftClicked, this, + [this, maxButton] { + this->setWindowState(maxButton->getButtonStyle() != TitleBarButtonStyle::Maximize ? Qt::WindowActive : Qt::WindowMaximized); - }); - QObject::connect(_exitButton, &TitleBarButton::leftClicked, - this, [this] { - this->close(); - }); - - this->ui_.titlebarButtons = new TitleBarButtons( - this, _minButton, _maxButton, _exitButton); - - this->ui_.buttons.push_back(_minButton); - this->ui_.buttons.push_back(_maxButton); - this->ui_.buttons.push_back(_exitButton); - - // buttonLayout->addStretch(1); - buttonLayout->addWidget(_minButton); - buttonLayout->addWidget(_maxButton); - buttonLayout->addWidget(_exitButton); - buttonLayout->setSpacing(0); - } + }); + QObject::connect(exitButton, &TitleBarButton::leftClicked, this, + [this] { + this->close(); + }); + + this->ui_.titlebarButtons = + new TitleBarButtons(this, minButton, maxButton, exitButton); + + this->ui_.buttons.push_back(minButton); + this->ui_.buttons.push_back(maxButton); + this->ui_.buttons.push_back(exitButton); + + buttonLayout->addWidget(minButton); + buttonLayout->addWidget(maxButton); + buttonLayout->addWidget(exitButton); + buttonLayout->setSpacing(0); } + this->ui_.layoutBase = new BaseWidget(this); this->ui_.layoutBase->setContentsMargins(1, 0, 1, 1); layout->addWidget(this->ui_.layoutBase); } - -// DPI -// auto dpi = getWindowDpi(this->safeHWND()); - -// if (dpi) { -// this->scale = dpi.value() / 96.f; -// } #endif // TopMost flag overrides setting @@ -563,29 +698,8 @@ void BaseWindow::resizeEvent(QResizeEvent *) } #ifdef USEWINSDK - if (this->hasCustomWindowFrame() && !this->isResizeFixing_) - { - this->isResizeFixing_ = true; - QTimer::singleShot(50, this, [this] { - auto hwnd = this->safeHWND(); - if (!hwnd) - { - this->isResizeFixing_ = false; - return; - } - RECT rect; - ::GetWindowRect(*hwnd, &rect); - ::SetWindowPos(*hwnd, nullptr, 0, 0, rect.right - rect.left + 1, - rect.bottom - rect.top, SWP_NOMOVE | SWP_NOZORDER); - ::SetWindowPos(*hwnd, nullptr, 0, 0, rect.right - rect.left, - rect.bottom - rect.top, SWP_NOMOVE | SWP_NOZORDER); - QTimer::singleShot(10, this, [this] { - this->isResizeFixing_ = false; - }); - }); - } - this->calcButtonsSizes(); + this->updateRealSize(); #endif } @@ -647,10 +761,6 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, switch (msg->message) { - case WM_DPICHANGED: - returnValue = this->handleDPICHANGED(msg); - break; - case WM_SHOWWINDOW: returnValue = this->handleSHOWWINDOW(msg); break; @@ -695,6 +805,7 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, RECT winrect; GetWindowRect(msg->hwnd, &winrect); QPoint globalPos(x, y); + globalPos /= this->devicePixelRatio(); this->ui_.titlebarButtons->hover(msg->wParam, globalPos); this->lastEventWasNcMouseMove_ = true; } @@ -746,6 +857,7 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, RECT winrect; GetWindowRect(msg->hwnd, &winrect); QPoint globalPos(x, y); + globalPos /= this->devicePixelRatio(); if (msg->message == WM_NCLBUTTONDOWN) { this->ui_.titlebarButtons->mousePress(ht, globalPos); @@ -807,6 +919,22 @@ void BaseWindow::updateScale() } } +#ifdef USEWINSDK +void BaseWindow::updateRealSize() +{ + auto hwnd = this->safeHWND(); + if (!hwnd) + { + return; + } + + RECT real; + ::GetWindowRect(*hwnd, &real); + this->realBounds_ = QRect(real.left, real.top, real.right - real.left, + real.bottom - real.top); +} +#endif + void BaseWindow::calcButtonsSizes() { if (!this->shown_) @@ -838,34 +966,28 @@ void BaseWindow::drawCustomWindowFrame(QPainter &painter) { QColor bg = this->overrideBackgroundColor_.value_or( this->theme->window.background); - painter.fillRect(QRect(1, 2, this->width() - 2, this->height() - 3), - bg); + if (this->isMaximized_) + { + painter.fillRect(this->rect(), bg); + } + else + { + // Draw a border that's exactly 1px wide + // + // There is a bug where the border can get px wide while dragging. + // this "fixes" itself when deselecting the window. + auto dpr = this->devicePixelRatio(); + if (dpr != 1) + { + painter.setTransform(QTransform::fromScale(1 / dpr, 1 / dpr)); + } + painter.fillRect(1, 1, this->realBounds_.width() - 2, + this->realBounds_.height() - 2, bg); + } } #endif } -bool BaseWindow::handleDPICHANGED(MSG *msg) -{ -#ifdef USEWINSDK - int dpi = HIWORD(msg->wParam); - - float _scale = dpi / 96.f; - - auto *prcNewWindow = reinterpret_cast(msg->lParam); - SetWindowPos(msg->hwnd, nullptr, prcNewWindow->left, prcNewWindow->top, - prcNewWindow->right - prcNewWindow->left, - prcNewWindow->bottom - prcNewWindow->top, - SWP_NOZORDER | SWP_NOACTIVATE); - - this->nativeScale_ = _scale; - this->updateScale(); - - return true; -#else - return false; -#endif -} - bool BaseWindow::handleSHOWWINDOW(MSG *msg) { #ifdef USEWINSDK @@ -875,16 +997,6 @@ bool BaseWindow::handleSHOWWINDOW(MSG *msg) return true; } - if (auto dpi = getWindowDpi(msg->hwnd)) - { - float currentScale = (float)dpi.value() / 96.F; - if (currentScale != this->nativeScale_) - { - this->nativeScale_ = currentScale; - this->updateScale(); - } - } - if (!this->shown_) { this->shown_ = true; @@ -898,14 +1010,12 @@ bool BaseWindow::handleSHOWWINDOW(MSG *msg) if (!this->initalBounds_.isNull()) { - ::SetWindowPos(msg->hwnd, nullptr, this->initalBounds_.x(), - this->initalBounds_.y(), this->initalBounds_.width(), - this->initalBounds_.height(), - SWP_NOZORDER | SWP_NOACTIVATE); + this->setGeometry(this->initalBounds_); this->currentBounds_ = this->initalBounds_; } this->calcButtonsSizes(); + this->updateRealSize(); } return true; @@ -921,23 +1031,54 @@ bool BaseWindow::handleNCCALCSIZE(MSG *msg, long *result) #endif { #ifdef USEWINSDK - if (this->hasCustomWindowFrame()) + if (!this->hasCustomWindowFrame()) { - if (msg->wParam == TRUE) - { - // remove 1 extra pixel on top of custom frame - auto *ncp = reinterpret_cast(msg->lParam); - if (ncp) - { - ncp->lppos->flags |= SWP_NOREDRAW; - ncp->rgrc[0].top -= 1; - } - } + return false; + } + if (msg->wParam != TRUE) + { *result = 0; return true; } - return false; + + auto *params = reinterpret_cast(msg->lParam); + auto *r = ¶ms->rgrc[0]; + + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + this->isMaximized_ = GetWindowPlacement(msg->hwnd, &wp) != 0 && + (wp.showCmd == SW_SHOWMAXIMIZED); + + auto borders = windowBordersFor(msg->hwnd, this->isMaximized_); + r->left += borders.left; + r->top += borders.top; + r->right += borders.right; + r->bottom += borders.bottom; + + if (borders.left != 0 || borders.top != 0 || borders.right != 0 || + borders.bottom != 0) + { + // We added borders -> we changed the rect, so we can't return + // WVR_VALIDRECTS + *result = 0; + return true; + } + + // This is an attempt at telling Windows to not redraw (or at least to do a + // better job at redrawing) the window. There is a long list of tricks + // people tried to prevent this at + // https://stackoverflow.com/q/53000291/16300717 + // + // We set the source and destination rectangles to a 1x1 rectangle at the + // top left. Windows is instructed by WVR_VALIDRECTS to copy and preserve + // some parts of the window image. + QPoint fixed = {r->left, r->top}; + params->rgrc[1] = {fixed.x(), fixed.y(), fixed.x() + 1, fixed.y() + 1}; + params->rgrc[2] = {fixed.x(), fixed.y(), fixed.x() + 1, fixed.y() + 1}; + *result = WVR_VALIDRECTS; + + return true; #else return false; #endif @@ -954,28 +1095,11 @@ bool BaseWindow::handleSIZE(MSG *msg) } else if (this->hasCustomWindowFrame()) { - if (msg->wParam == SIZE_MAXIMIZED) - { - auto offset = - int(getWindowDpi(msg->hwnd).value_or(96) * 8 / 96); - - this->ui_.windowLayout->setContentsMargins(offset, offset, - offset, offset); - } - else - { - this->ui_.windowLayout->setContentsMargins(0, 1, 0, 0); - } - this->isNotMinimizedOrMaximized_ = msg->wParam == SIZE_RESTORED; if (this->isNotMinimizedOrMaximized_) { - RECT rect; - ::GetWindowRect(msg->hwnd, &rect); - this->currentBounds_ = - QRect(QPoint(rect.left, rect.top), - QPoint(rect.right - 1, rect.bottom - 1)); + this->currentBounds_ = this->geometry(); } this->useNextBounds_.stop(); @@ -985,6 +1109,12 @@ bool BaseWindow::handleSIZE(MSG *msg) // the minimize button, so we have to emulate it. this->ui_.titlebarButtons->leave(); } + + RECT real; + ::GetWindowRect(msg->hwnd, &real); + this->realBounds_ = + QRect(real.left, real.top, real.right - real.left, + real.bottom - real.top); } } return false; @@ -998,11 +1128,7 @@ bool BaseWindow::handleMOVE(MSG *msg) #ifdef USEWINSDK if (this->isNotMinimizedOrMaximized_) { - RECT rect; - ::GetWindowRect(msg->hwnd, &rect); - this->nextBounds_ = QRect(QPoint(rect.left, rect.top), - QPoint(rect.right - 1, rect.bottom - 1)); - + this->nextBounds_ = this->geometry(); this->useNextBounds_.start(10); } #endif @@ -1016,7 +1142,7 @@ bool BaseWindow::handleNCHITTEST(MSG *msg, long *result) #endif { #ifdef USEWINSDK - const LONG border_width = 8; // in pixels + const LONG borderWidth = 8; // in pixels RECT winrect; GetWindowRect(msg->hwnd, &winrect); @@ -1024,6 +1150,7 @@ bool BaseWindow::handleNCHITTEST(MSG *msg, long *result) long y = GET_Y_LPARAM(msg->lParam); QPoint point(x - winrect.left, y - winrect.top); + point /= this->devicePixelRatio(); if (this->hasCustomWindowFrame()) { @@ -1035,12 +1162,12 @@ bool BaseWindow::handleNCHITTEST(MSG *msg, long *result) if (resizeWidth) { // left border - if (x < winrect.left + border_width) + if (x < winrect.left + borderWidth) { *result = HTLEFT; } // right border - if (x >= winrect.right - border_width) + if (x >= winrect.right - borderWidth) { *result = HTRIGHT; } @@ -1048,12 +1175,12 @@ bool BaseWindow::handleNCHITTEST(MSG *msg, long *result) if (resizeHeight) { // bottom border - if (y >= winrect.bottom - border_width) + if (y >= winrect.bottom - borderWidth) { *result = HTBOTTOM; } // top border - if (y < winrect.top + border_width) + if (y < winrect.top + borderWidth) { *result = HTTOP; } @@ -1061,26 +1188,26 @@ bool BaseWindow::handleNCHITTEST(MSG *msg, long *result) if (resizeWidth && resizeHeight) { // bottom left corner - if (x >= winrect.left && x < winrect.left + border_width && - y < winrect.bottom && y >= winrect.bottom - border_width) + if (x >= winrect.left && x < winrect.left + borderWidth && + y < winrect.bottom && y >= winrect.bottom - borderWidth) { *result = HTBOTTOMLEFT; } // bottom right corner - if (x < winrect.right && x >= winrect.right - border_width && - y < winrect.bottom && y >= winrect.bottom - border_width) + if (x < winrect.right && x >= winrect.right - borderWidth && + y < winrect.bottom && y >= winrect.bottom - borderWidth) { *result = HTBOTTOMRIGHT; } // top left corner - if (x >= winrect.left && x < winrect.left + border_width && - y >= winrect.top && y < winrect.top + border_width) + if (x >= winrect.left && x < winrect.left + borderWidth && + y >= winrect.top && y < winrect.top + borderWidth) { *result = HTTOPLEFT; } // top right corner - if (x < winrect.right && x >= winrect.right - border_width && - y >= winrect.top && y < winrect.top + border_width) + if (x < winrect.right && x >= winrect.right - borderWidth && + y >= winrect.top && y < winrect.top + borderWidth) { *result = HTTOPRIGHT; } diff --git a/src/widgets/BaseWindow.hpp b/src/widgets/BaseWindow.hpp index e59c8ae8173..db15aec1a03 100644 --- a/src/widgets/BaseWindow.hpp +++ b/src/widgets/BaseWindow.hpp @@ -131,7 +131,6 @@ class BaseWindow : public BaseWidget void drawCustomWindowFrame(QPainter &painter); void onFocusLost(); - bool handleDPICHANGED(MSG *msg); bool handleSHOWWINDOW(MSG *msg); bool handleSIZE(MSG *msg); bool handleMOVE(MSG *msg); @@ -149,7 +148,6 @@ class BaseWindow : public BaseWidget bool shown_ = false; FlagsEnum flags_; float nativeScale_ = 1; - bool isResizeFixing_ = false; bool isTopMost_ = false; struct { @@ -167,6 +165,7 @@ class BaseWindow : public BaseWidget widgets::BoundsChecking lastBoundsCheckMode_ = widgets::BoundsChecking::Off; #ifdef USEWINSDK + void updateRealSize(); /// @brief Returns the HWND of this window if it has one /// /// A QWidget only has an HWND if it has been created. Before that, @@ -192,6 +191,10 @@ class BaseWindow : public BaseWidget QTimer useNextBounds_; bool isNotMinimizedOrMaximized_{}; bool lastEventWasNcMouseMove_ = false; + /// The real bounds of the window as returned by + /// GetWindowRect. Used for drawing. + QRect realBounds_; + bool isMaximized_ = false; #endif pajlada::Signals::SignalHolder connections_; diff --git a/src/widgets/Label.cpp b/src/widgets/Label.cpp index 37bd9df3601..1d3e7067f6c 100644 --- a/src/widgets/Label.cpp +++ b/src/widgets/Label.cpp @@ -88,23 +88,10 @@ void Label::paintEvent(QPaintEvent *) { QPainter painter(this); - qreal deviceDpi = -#ifdef Q_OS_WIN - this->devicePixelRatioF(); -#else - 1.0; -#endif - QFontMetrics metrics = getIApp()->getFonts()->getFontMetrics( - this->getFontStyle(), - this->scale() * 96.f / - std::max( - 0.01F, static_cast(this->logicalDpiX() * deviceDpi))); - painter.setFont(getIApp()->getFonts()->getFont( - this->getFontStyle(), - this->scale() * 96.f / - std::max( - 0.02F, static_cast(this->logicalDpiX() * deviceDpi)))); + this->getFontStyle(), this->scale()); + painter.setFont( + getIApp()->getFonts()->getFont(this->getFontStyle(), this->scale())); int offset = this->getOffset(); diff --git a/src/widgets/TooltipEntryWidget.cpp b/src/widgets/TooltipEntryWidget.cpp index 7ef7274e385..0f0ac5336b5 100644 --- a/src/widgets/TooltipEntryWidget.cpp +++ b/src/widgets/TooltipEntryWidget.cpp @@ -86,6 +86,7 @@ bool TooltipEntryWidget::refreshPixmap() this->attemptRefresh_ = true; return false; } + pixmap->setDevicePixelRatio(this->devicePixelRatio()); if (this->customImgWidth_ > 0 || this->customImgHeight_ > 0) { diff --git a/src/widgets/dialogs/SettingsDialog.cpp b/src/widgets/dialogs/SettingsDialog.cpp index 49cdf8e3539..bf6c575167a 100644 --- a/src/widgets/dialogs/SettingsDialog.cpp +++ b/src/widgets/dialogs/SettingsDialog.cpp @@ -47,7 +47,10 @@ SettingsDialog::SettingsDialog(QWidget *parent) this->resize(915, 600); this->themeChangedEvent(); - this->scaleChangedEvent(this->scale()); + QFile styleFile(":/qss/settings.qss"); + styleFile.open(QFile::ReadOnly); + QString stylesheet = QString::fromUtf8(styleFile.readAll()); + this->setStyleSheet(stylesheet); this->initUi(); this->addTabs(); @@ -391,25 +394,19 @@ void SettingsDialog::refresh() void SettingsDialog::scaleChangedEvent(float newDpi) { - QFile file(":/qss/settings.qss"); - file.open(QFile::ReadOnly); - QString styleSheet = QLatin1String(file.readAll()); - styleSheet.replace("", QString::number(int(14 * newDpi))); - styleSheet.replace("", QString::number(int(14 * newDpi))); + assert(newDpi == 1.F && + "Scaling is disabled for the settings dialog - its scale should " + "always be 1"); for (SettingsDialogTab *tab : this->tabs_) { - tab->setFixedHeight(int(30 * newDpi)); + tab->setFixedHeight(30); } - this->setStyleSheet(styleSheet); - if (this->ui_.tabContainerContainer) { - this->ui_.tabContainerContainer->setFixedWidth(int(150 * newDpi)); + this->ui_.tabContainerContainer->setFixedWidth(150); } - - this->dpi_ = newDpi; } void SettingsDialog::themeChangedEvent() diff --git a/src/widgets/helper/Button.cpp b/src/widgets/helper/Button.cpp index 08dde78e146..0574326666a 100644 --- a/src/widgets/helper/Button.cpp +++ b/src/widgets/helper/Button.cpp @@ -8,26 +8,44 @@ #include #include -namespace chatterino { namespace { - // returns a new resized image or the old one if the size didn't change - auto resizePixmap(const QPixmap ¤t, const QPixmap resized, - const QSize &size) -> QPixmap +QSizeF deviceIndependentSize(const QPixmap &pixmap) +{ +#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0) + return QSizeF(pixmap.width(), pixmap.height()) / pixmap.devicePixelRatio(); +#else + return pixmap.deviceIndependentSize(); +#endif +} + +/** + * Resizes a pixmap to a desired size. + * Does nothing if the target pixmap is already sized correctly. + * + * @param target The target pixmap. + * @param source The unscaled pixmap. + * @param size The desired device independent size. + * @param dpr The device pixel ratio of the target area. The size of the target in pixels will be `size * dpr`. + */ +void resizePixmap(QPixmap &target, const QPixmap &source, const QSize &size, + qreal dpr) +{ + if (deviceIndependentSize(target) == size) { - if (resized.size() == size) - { - return resized; - } - else - { - return current.scaled(size, Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - } + return; } + QPixmap resized = source; + resized.setDevicePixelRatio(dpr); + target = resized.scaled(size * dpr, Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); +} + } // namespace +namespace chatterino { + Button::Button(BaseWidget *parent) : BaseWidget(parent) { @@ -47,6 +65,12 @@ void Button::setMouseEffectColor(std::optional color) void Button::setPixmap(const QPixmap &_pixmap) { + // Avoid updates if the pixmap didn't change + if (_pixmap.cacheKey() == this->pixmap_.cacheKey()) + { + return; + } + this->pixmap_ = _pixmap; this->resizedPixmap_ = {}; this->update(); @@ -158,8 +182,8 @@ void Button::paintButton(QPainter &painter) QRect rect = this->rect(); - this->resizedPixmap_ = - resizePixmap(this->pixmap_, this->resizedPixmap_, rect.size()); + resizePixmap(this->resizedPixmap_, this->pixmap_, rect.size(), + this->devicePixelRatio()); int margin = this->height() < 22 * this->scale() ? 3 : 6; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 1454b4999de..e8478ee7221 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -686,8 +686,10 @@ void ChannelView::layoutVisibleMessages( { const auto &message = messages[i]; - redrawRequired |= message->layout(layoutWidth, this->scale(), flags, - this->bufferInvalidationQueued_); + redrawRequired |= message->layout( + layoutWidth, this->scale(), + this->scale() * static_cast(this->devicePixelRatio()), + flags, this->bufferInvalidationQueued_); y += message->getHeight(); } @@ -721,7 +723,10 @@ void ChannelView::updateScrollbar( { auto *message = messages[i].get(); - message->layout(layoutWidth, this->scale(), flags, false); + message->layout( + layoutWidth, this->scale(), + this->scale() * static_cast(this->devicePixelRatio()), flags, + false); h -= message->getHeight(); @@ -1699,9 +1704,11 @@ void ChannelView::wheelEvent(QWheelEvent *event) } else { - snapshot[i - 1]->layout(this->getLayoutWidth(), - this->scale(), this->getFlags(), - false); + snapshot[i - 1]->layout( + this->getLayoutWidth(), this->scale(), + this->scale() * + static_cast(this->devicePixelRatio()), + this->getFlags(), false); scrollFactor = 1; currentScrollLeft = snapshot[i - 1]->getHeight(); } @@ -1734,9 +1741,11 @@ void ChannelView::wheelEvent(QWheelEvent *event) } else { - snapshot[i + 1]->layout(this->getLayoutWidth(), - this->scale(), this->getFlags(), - false); + snapshot[i + 1]->layout( + this->getLayoutWidth(), this->scale(), + this->scale() * + static_cast(this->devicePixelRatio()), + this->getFlags(), false); scrollFactor = 1; currentScrollLeft = snapshot[i + 1]->getHeight(); diff --git a/src/widgets/helper/NotebookTab.cpp b/src/widgets/helper/NotebookTab.cpp index da04562fae2..39b282df2f8 100644 --- a/src/widgets/helper/NotebookTab.cpp +++ b/src/widgets/helper/NotebookTab.cpp @@ -27,15 +27,6 @@ namespace chatterino { namespace { - qreal deviceDpi(QWidget *widget) - { -#ifdef Q_OS_WIN - return widget->devicePixelRatioF(); -#else - return 1.0; -#endif - } - // Translates the given rectangle by an amount in the direction to appear like the tab is selected. // For example, if location is Top, the rectangle will be translated in the negative Y direction, // or "up" on the screen, by amount. @@ -196,8 +187,8 @@ int NotebookTab::normalTabWidth() float scale = this->scale(); int width; - auto metrics = getIApp()->getFonts()->getFontMetrics( - FontStyle::UiTabs, float(qreal(this->scale()) * deviceDpi(this))); + QFontMetrics metrics = + getApp()->getFonts()->getFontMetrics(FontStyle::UiTabs, this->scale()); if (this->hasXButton()) { @@ -439,11 +430,9 @@ void NotebookTab::paintEvent(QPaintEvent *) QPainter painter(this); float scale = this->scale(); - auto div = std::max(0.01f, this->logicalDpiX() * deviceDpi(this)); - painter.setFont( - getIApp()->getFonts()->getFont(FontStyle::UiTabs, scale * 96.f / div)); + painter.setFont(app->getFonts()->getFont(FontStyle::UiTabs, scale)); QFontMetrics metrics = - app->getFonts()->getFontMetrics(FontStyle::UiTabs, scale * 96.f / div); + app->getFonts()->getFontMetrics(FontStyle::UiTabs, scale); int height = int(scale * NOTEBOOK_TAB_HEIGHT); diff --git a/tests/src/MessageLayout.cpp b/tests/src/MessageLayout.cpp index 9ce0c7f21e8..094bed3d8ba 100644 --- a/tests/src/MessageLayout.cpp +++ b/tests/src/MessageLayout.cpp @@ -63,7 +63,7 @@ class MessageLayoutTest builder.append( std::make_unique(text, MessageElementFlag::Text)); this->layout = std::make_unique(builder.release()); - this->layout->layout(WIDTH, 1, MessageElementFlag::Text, false); + this->layout->layout(WIDTH, 1, 1, MessageElementFlag::Text, false); } MockApplication mockApplication;