From 438aa1524ee99fd636dc02a7181857ade71bb101 Mon Sep 17 00:00:00 2001 From: Wladimir Leuschner Date: Wed, 4 Dec 2024 16:43:53 +0100 Subject: [PATCH] WindowsQPA: Draw custom titlebar with QPainter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Draw custom titlebars that are requested by setting the Qt::ExpandedClientAreaHint with QPainter instead of GdiPlus. - Draw the application icon, in case it was set, for the custom titlebar - Add DPI awareness to the custom titlebar Pick-to: 6.9 Change-Id: I276e7d8948e5a436f1835d96b59756b7237f63d2 Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/direct2d/CMakeLists.txt | 1 - src/plugins/platforms/windows/CMakeLists.txt | 1 - .../platforms/windows/qwindowscontext.cpp | 11 - .../platforms/windows/qwindowswindow.cpp | 229 ++++++++++-------- 4 files changed, 126 insertions(+), 116 deletions(-) diff --git a/src/plugins/platforms/direct2d/CMakeLists.txt b/src/plugins/platforms/direct2d/CMakeLists.txt index de1d5e64fd0..8d63de1d051 100644 --- a/src/plugins/platforms/direct2d/CMakeLists.txt +++ b/src/plugins/platforms/direct2d/CMakeLists.txt @@ -64,7 +64,6 @@ qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin dxgi dxguid gdi32 - gdiplus uxtheme imm32 ole32 diff --git a/src/plugins/platforms/windows/CMakeLists.txt b/src/plugins/platforms/windows/CMakeLists.txt index ba702a372e9..9fc555504bc 100644 --- a/src/plugins/platforms/windows/CMakeLists.txt +++ b/src/plugins/platforms/windows/CMakeLists.txt @@ -52,7 +52,6 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin advapi32 dwmapi gdi32 - gdiplus uxtheme imm32 ole32 diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index 649cb35d459..58514c96e1d 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -58,7 +58,6 @@ #include #include #include -#include QT_BEGIN_NAMESPACE @@ -134,7 +133,6 @@ QWindowsContext *QWindowsContext::m_instance = nullptr; struct QWindowsContextPrivate { QWindowsContextPrivate(); - ~QWindowsContextPrivate(); unsigned m_systemInfo = 0; QSet m_registeredWindowClassNames; @@ -149,7 +147,6 @@ struct QWindowsContextPrivate { #if QT_CONFIG(tabletevent) QScopedPointer m_tabletSupport; #endif - ULONG_PTR m_gdiplusToken = 0; const HRESULT m_oleInitializeResult; QWindow *m_lastActiveWindow = nullptr; bool m_asyncExpose = false; @@ -175,14 +172,6 @@ QWindowsContextPrivate::QWindowsContextPrivate() qWarning() << "QWindowsContext: OleInitialize() failed: " << QSystemError::windowsComString(m_oleInitializeResult); } - - Gdiplus::GdiplusStartupInput gdiplusStartupInput; - Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, nullptr); -} - -QWindowsContextPrivate::~QWindowsContextPrivate() -{ - Gdiplus::GdiplusShutdown(m_gdiplusToken); } QWindowsContext::QWindowsContext() : diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 8b065bc33bc..0dc295cc8ce 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include // QWINDOWSIZE_MAX #include @@ -3335,10 +3336,14 @@ bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *re *result == HTCLIENT){ QWindow* wnd = window(); auto buttons = GetAsyncKeyState(VK_LBUTTON) != 0 ? Qt::LeftButton : Qt::NoButton; - QMouseEvent event(QEvent::MouseButtonPress, localPos, globalPos, buttons, buttons, Qt::NoModifier); - QGuiApplication::sendEvent(wnd, &event); - if (!event.isAccepted()) - *result = HTCAPTION; + if (buttons != Qt::NoButton) { + QMouseEvent event(QEvent::MouseButtonPress, localPos, globalPos, buttons, buttons, Qt::NoModifier); + QGuiApplication::sendEvent(wnd, &event); + if (!event.isAccepted() && GetAsyncKeyState(VK_RBUTTON)) + *result = HTSYSMENU; + else if (!event.isAccepted()) + *result = HTCAPTION; + } } } } @@ -3371,6 +3376,11 @@ bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *re case HTSIZE: const_cast(w)->showNormal(); break; + case HTSYSMENU: { + HWND hwnd = reinterpret_cast(w->winId()); + HMENU sysMenu = GetSystemMenu(hwnd, false); + TrackPopupMenu(sysMenu, 0, globalPos.x(), globalPos.y(), 0, hwnd, nullptr); + } default: break; } @@ -3440,18 +3450,17 @@ bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *re return false; } -static void _q_drawCustomTitleBarButton(Gdiplus::Graphics& graphics, Gdiplus::RectF& r, Gdiplus::SolidBrush& brush) +static void _q_drawCustomTitleBarButton(QPainter& p, const QRectF& r) { - r.Height += 1; - Gdiplus::GraphicsPath path; - Gdiplus::RectF Corner(r.X + r.Width - 4, r.Y, 2, 2); - path.Reset(); - path.AddLine(r.X, r.Y, r.X + r.Width - 4.0f, r.Y); - path.AddArc(Corner, 90, 90); - path.AddLine(r.X + r.Width, r.Y + 4, r.X + r.Width, r.Y + r.Height - 2); - path.AddLine(r.X + r.Width, r.Y + r.Height - 2, r.X, r.Y + r.Height - 2); - path.CloseFigure(); - graphics.FillPath(&brush, &path); + QPainterPath path(QPointF(r.x(), r.y())); + QRectF rightCorner(r.x() + r.width() - 2.0, r.y() + 4.0, 2, 2); + QRectF leftCorner(r.x(), r.y() + 4, 2, 2); + path.lineTo(r.x() + r.width() - 5.0f, r.y()); + path.arcTo(rightCorner, 90, -90); + path.lineTo(r.x() + r.width(), r.y() + r.height() - 1); + path.lineTo(r.x(), r.y() + r.height() - 1); + path.closeSubpath(); + p.drawPath(path); } void QWindowsWindow::updateCustomTitlebar() @@ -3464,137 +3473,151 @@ void QWindowsWindow::updateCustomTitlebar() const int titleBarHeight = getTitleBarHeight_sys(savedDpi()); const int titleButtonWidth = titleBarHeight * 1.5; + const qreal factor = QHighDpiScaling::factor(wnd); const int windowWidth = windowRect.right - windowRect.left; POINT localPos; GetCursorPos(&localPos); MapWindowPoints(HWND_DESKTOP, hwnd, &localPos, 1); - bool isDarkmode = QWindowsIntegration::instance()->darkModeHandling().testFlags(QWindowsApplication::DarkModeWindowFrames) && + const bool isDarkmode = QWindowsIntegration::instance()->darkModeHandling().testFlags(QWindowsApplication::DarkModeWindowFrames) && qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark; - const bool isWindows11orAbove = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11; - const wchar_t *iconFontName = isWindows11orAbove ? L"Segoe Fluent Icons" : L"Segoe MDL2 Assets"; - const std::wstring titleTextFontName = QWindowsIntegration::instance()->fontDatabase()->defaultFont().family().toStdWString(); - Gdiplus::Bitmap softwareBitmap(windowWidth, titleBarHeight, PixelFormat32bppARGB); - Gdiplus::Graphics graphics(&softwareBitmap); - graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit); - Gdiplus::SolidBrush iconBrush(isDarkmode ? Gdiplus::Color(255, 255, 255, 255) : Gdiplus::Color(255, 0, 0, 0)); - Gdiplus::FontFamily titleFontFamily(titleTextFontName.data()); - Gdiplus::Font titleFont(&titleFontFamily, 14, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel); - Gdiplus::FontFamily iconFontFamily(iconFontName); - Gdiplus::Font iconFont(&iconFontFamily, 8, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel); - Gdiplus::StringFormat titleFormat; - titleFormat.SetAlignment(Gdiplus::StringAlignmentNear); - titleFormat.SetLineAlignment(Gdiplus::StringAlignmentCenter); - Gdiplus::StringFormat buttonFormat; - buttonFormat.SetAlignment(Gdiplus::StringAlignmentCenter); - buttonFormat.SetLineAlignment(Gdiplus::StringAlignmentCenter); + const QBrush closeButtonBrush(QColor(0xC4, 0x2B, 0x1C, 255)); + const QBrush minMaxButtonBrush = QBrush(isDarkmode ? QColor(0xFF, 0xFF, 0xFF, 0x40) : QColor(0x00, 0x00, 0x00, 0x20)); + const QBrush titleBarBackgroundColor = QBrush(isDarkmode ? QColor(0x1F, 0x1F, 0x1F, 0xFF) : QColor(0xF3, 0xF3, 0xF3, 0xFF)); + const QPen textPen = QPen(isDarkmode ? QColor(255, 255, 255, 255) : QColor(0, 0, 0, 255)); - Gdiplus::SolidBrush closeButtonBrush(Gdiplus::Color(255, 255, 0, 0)); - Gdiplus::SolidBrush minMaxButtonBrush = Gdiplus::SolidBrush(isDarkmode ? Gdiplus::Color(0x40, 0xFF, 0xFF, 0xFF) : Gdiplus::Color(0x20, 0x00, 0x00, 0x00)); + QImage image(windowWidth, titleBarHeight, QImage::Format_ARGB32); + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_Clear); + p.fillRect(0, 0, windowWidth, titleBarHeight, Qt::transparent); - Gdiplus::Color titleBarBackgroundColor = isDarkmode ? Gdiplus::Color(0xFF, 0x1F, 0x1F, 0x1F) : Gdiplus::Color(0xFF, 0xF3, 0xF3, 0xF3); - Gdiplus::SolidBrush titleBarBackgroundBrush(titleBarBackgroundColor); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); - int buttons = 1; - Gdiplus::RectF titleRect; - - graphics.Clear(Gdiplus::Color(0x00, 0x00, 0x00, 0x00)); + p.setBrush(titleBarBackgroundColor); + p.setPen(Qt::NoPen); if (!wnd->flags().testFlags(Qt::NoTitleBarBackgroundHint)) { - titleRect.Y = 1; - titleRect.X = 1; - titleRect.Width = windowWidth - 2; - titleRect.Height = titleBarHeight; + QRect titleRect; + titleRect.setY(1); + titleRect.setX(2); + titleRect.setWidth(windowWidth - 2); + titleRect.setHeight(titleBarHeight); if (isWindows11orAbove) { - Gdiplus::GraphicsPath path; - titleRect.Height += 1; - Gdiplus::RectF rightCorner(titleRect.X + titleRect.Width - 4, titleRect.Y, 2, 2); - Gdiplus::RectF leftCorner(titleRect.X, titleRect.Y + 4, 2, 2); - path.Reset(); - path.AddLine(titleRect.X + 4.0f, titleRect.Y, titleRect.X + titleRect.Width - 4.0f, titleRect.Y); - path.AddArc(rightCorner, 90, 90); - path.AddLine(titleRect.X + titleRect.Width, titleRect.Y + 4, titleRect.X + titleRect.Width, titleRect.Y + titleRect.Height - 2); - path.AddLine(titleRect.X + titleRect.Width, titleRect.Y + titleRect.Height - 2, titleRect.X, titleRect.Y + titleRect.Height - 2); - path.AddLine(titleRect.X, titleRect.Y + titleRect.Height - 2, titleRect.X, titleRect.Y + 4.0f); - path.AddArc(leftCorner, -90, -90); - path.CloseFigure(); - graphics.FillPath(&titleBarBackgroundBrush, &path); + QPainterPath path(QPointF(titleRect.x() + 4.0f, titleRect.y())); + QRectF rightCorner(titleRect.x() + titleRect.width() - 4.0, titleRect.y() + 4.0, 2, 2); + QRectF leftCorner(titleRect.x(), titleRect.y() + 4, 2, 2); + path.lineTo(titleRect.x() + titleRect.width() - 7.0f, titleRect.y()); + path.arcTo(rightCorner, 90, -90); + path.lineTo(titleRect.x() + titleRect.width() - 2.0, titleRect.y() + titleRect.height() - 1); + path.lineTo(titleRect.x(), titleRect.y() + titleRect.height() - 1); + path.lineTo(titleRect.x(), titleRect.y() + 4.0f); + path.arcTo(leftCorner, -90, -90); + path.closeSubpath(); + p.drawPath(path); } else { - graphics.FillRectangle(&titleBarBackgroundBrush, titleRect); + p.drawRect(titleRect); } } + if (wnd->flags().testFlags(Qt::WindowTitleHint | Qt::CustomizeWindowHint) || !wnd->flags().testFlag(Qt::CustomizeWindowHint)) { - titleRect.Y = 1; - titleRect.X = 12; - titleRect.Width = windowWidth; - titleRect.Height = titleBarHeight; + QRect titleRect; + titleRect.setY(1); + titleRect.setX(12); + titleRect.setWidth(windowWidth); + titleRect.setHeight(titleBarHeight); - std::wstring titleString = wnd->title().toStdWString(); - graphics.DrawString(titleString.c_str(), -1, &titleFont, titleRect, &titleFormat, &iconBrush); + const QIcon icon = wnd->icon(); + if (!icon.isNull()) { + titleRect.adjust(factor * 4, 0, 0, 0); + QRect iconRect(titleRect.x(), titleRect.y() + factor * 8, factor * 16, factor * 16); + icon.paint(&p, iconRect); + titleRect.adjust(factor * 24, 0, 0, 0); + } + + p.setPen(textPen); + QFont titleFont = QWindowsIntegration::instance()->fontDatabase()->defaultFont(); + titleFont.setPointSize(factor * 9); + titleFont.setWeight(QFont::Thin); + titleFont.setHintingPreference(QFont::PreferFullHinting); + p.setFont(titleFont); + p.drawText(titleRect, wnd->title(), QTextOption(Qt::AlignVCenter)); } + int buttons = 1; + const QString assetFontName = isWindows11orAbove ? QStringLiteral("Segoe Fluent Icons") : QStringLiteral("Segoe MDL2 Assets"); + QFont assetFont = QFont(assetFontName, factor * 7); + assetFont.setWeight(QFont::Thin); + assetFont.setHintingPreference(QFont::PreferFullHinting); + p.setFont(assetFont); + p.setBrush(closeButtonBrush); + p.setPen(Qt::NoPen); if (wnd->flags().testFlags(Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint) || !wnd->flags().testFlag(Qt::CustomizeWindowHint)) { - Gdiplus::RectF rect; - rect.Y = 1; - rect.X = windowWidth - titleButtonWidth * buttons; - rect.Width = titleButtonWidth - 1; - rect.Height = titleBarHeight; + QRectF rect; + rect.setY(1); + rect.setX(windowWidth - titleButtonWidth * buttons); + rect.setWidth(titleButtonWidth - 1); + rect.setHeight(titleBarHeight); if (localPos.x > (windowWidth - buttons * titleButtonWidth) && localPos.x < (windowWidth - (buttons - 1) * titleButtonWidth) && - localPos.y > rect.Y && localPos.y < rect.Y + rect.Height) { - if (isWindows11orAbove && buttons == 1) { - _q_drawCustomTitleBarButton(graphics, rect, closeButtonBrush); - } else { - graphics.FillRectangle(&closeButtonBrush, rect); - } + localPos.y > rect.y() && localPos.y < rect.y() + rect.height()) { + if (isWindows11orAbove && buttons == 1) + _q_drawCustomTitleBarButton(p, rect); + else + p.drawRect(rect); } - graphics.DrawString(L"\uE8BB", -1, &iconFont, rect, &buttonFormat, &iconBrush); + p.setPen(textPen); + p.drawText(rect,QStringLiteral("\uE8BB"), QTextOption(Qt::AlignVCenter | Qt::AlignHCenter)); buttons++; } + p.setBrush(minMaxButtonBrush); + p.setPen(Qt::NoPen); if (wnd->flags().testFlags(Qt::WindowMaximizeButtonHint | Qt::CustomizeWindowHint) || !wnd->flags().testFlag(Qt::CustomizeWindowHint)) { - Gdiplus::RectF rect; - rect.Y = 1; - rect.X = windowWidth - titleButtonWidth * buttons; - rect.Width = titleButtonWidth - 1; - rect.Height = titleBarHeight; + QRectF rect; + rect.setY(1); + rect.setX(windowWidth - titleButtonWidth * buttons); + rect.setWidth(titleButtonWidth - 1); + rect.setHeight(titleBarHeight); if (localPos.x > (windowWidth - buttons * titleButtonWidth) && localPos.x < (windowWidth - (buttons - 1) * titleButtonWidth) && - localPos.y > rect.Y && localPos.y < rect.Y + rect.Height) { - if (isWindows11orAbove && buttons == 1) { - _q_drawCustomTitleBarButton(graphics, rect, minMaxButtonBrush); - } else { - graphics.FillRectangle(&minMaxButtonBrush, rect); - } + localPos.y > rect.y() && localPos.y < rect.y() + rect.height()) { + if (isWindows11orAbove && buttons == 1) + _q_drawCustomTitleBarButton(p, rect); + else + p.drawRect(rect); } - graphics.DrawString(L"\uE922", -1, &iconFont, rect, &buttonFormat, &iconBrush); + p.setPen(textPen); + p.drawText(rect,QStringLiteral("\uE922"), QTextOption(Qt::AlignVCenter | Qt::AlignHCenter)); buttons++; } + p.setBrush(minMaxButtonBrush); + p.setPen(Qt::NoPen); if (wnd->flags().testFlags(Qt::WindowMinimizeButtonHint | Qt::CustomizeWindowHint) || !wnd->flags().testFlag(Qt::CustomizeWindowHint)) { - Gdiplus::RectF rect; - rect.Y = 1; - rect.X = windowWidth - titleButtonWidth * buttons; - rect.Width = titleButtonWidth - 1; - rect.Height = titleBarHeight; + QRectF rect; + rect.setY(1); + rect.setX(windowWidth - titleButtonWidth * buttons); + rect.setWidth(titleButtonWidth - 1); + rect.setHeight(titleBarHeight); if (localPos.x > (windowWidth - buttons * titleButtonWidth) && localPos.x < (windowWidth - (buttons - 1) * titleButtonWidth) && - localPos.y > rect.Y && localPos.y < rect.Y + rect.Height) { - if (isWindows11orAbove && buttons == 1) { - _q_drawCustomTitleBarButton(graphics, rect, minMaxButtonBrush); - } else { - graphics.FillRectangle(&minMaxButtonBrush, rect); - } + localPos.y > rect.y() && localPos.y < rect.y() + rect.height()) { + if (isWindows11orAbove && buttons == 1) + _q_drawCustomTitleBarButton(p, rect); + else + p.drawRect(rect); } - graphics.DrawString(L"\uE921", -1, &iconFont, rect, &buttonFormat, &iconBrush); + p.setPen(textPen); + p.drawText(rect,QStringLiteral("\uE921"), QTextOption(Qt::AlignVCenter | Qt::AlignHCenter)); buttons++; } - HBITMAP bmp; - softwareBitmap.GetHBITMAP(Gdiplus::Color(0, 0, 0, 0), &bmp); + p.end(); + + HBITMAP bmp = image.toHBITMAP(); HDC hdc = GetDC(hwnd);