QtCanpool 3.3.0
载入中...
搜索中...
未找到
windowagentwidget_win.cpp
浏览该文件的文档.
1
6#include "windowkit_p.h"
7#include "windowkit_win.h"
8#include <QtCore/QDateTime>
9#include <QtCore/QDebug>
10#include <QtGui/QPainter>
11
13
14#if defined(QX_WINDOW_ENABLE_SYSTEM_BORDERS)
15// https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/plugins/platforms/windows/qwindowsbackingstore.cpp#L42
16// In QtWidgets applications, when repainting happens, QPA at the last calls
17// QWindowsBackingStore::flush() to draw the contents of the buffer to the screen, we need to
18// call GDI drawing the top border after that.
19
20// After debugging, we know that there are two situations that will lead to repaint.
21//
22// 1. Windows sends a WM_PAINT message, after which Qt immediately generates a QExposeEvent or
23// QResizeEvent and send it to the corresponding QWidgetWindow instance, calling "flush" at the
24// end of its handler.
25//
26// 2. When a timer or user input triggers Qt to repaint spontaneously, the corresponding
27// QWidget receives a QEvent::UpdateRequest event and also calls "flush" at the end of its
28// handler.
29//
30// The above two cases are mutually exclusive, so we just need to intercept the two events
31// separately and draw the border area after the "flush" is called.
32
33// https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/plugins/platforms/windows/qwindowswindow.cpp#L2440
34// Note that we can not draw the border right after WM_PAINT comes or right before the WndProc
35// returns, because Qt calls BeginPaint() and EndPaint() itself. We should make sure that we
36// draw the top border between these two calls, otherwise some display exceptions may arise.
37
38class WidgetBorderHandler : public QObject, public WindowNativeEventFilter, public WindowSharedEventFilter
39{
40public:
41 explicit WidgetBorderHandler(QWidget *widget, WindowContext *ctx, QObject *parent = nullptr)
42 : QObject(parent)
43 , widget(widget)
44 , ctx(ctx)
45 {
46 widget->installEventFilter(this);
47
48 // https://github.com/microsoft/terminal/blob/71a6f26e6ece656084e87de1a528c4a8072eeabd/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp#L940
49 // Must extend top frame to client area
50 static QVariant defaultMargins = QVariant::fromValue(QMargins(0, 1, 0, 0));
51 ctx->setWindowAttribute(QStringLiteral("extra-margins"), defaultMargins);
52
53 // Enable dark mode by default, otherwise the system borders are white
54 ctx->setWindowAttribute(QStringLiteral("dark-mode"), true);
55
56 ctx->installNativeEventFilter(this);
57 ctx->installSharedEventFilter(this);
58
59 updateGeometry();
60 }
61
62 inline bool isNormalWindow() const
63 {
64 return !(widget->windowState() & (Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen));
65 }
66
67 inline void updateGeometry()
68 {
69 if (isNormalWindow()) {
70 widget->setContentsMargins({0, ctx->windowAttribute(QStringLiteral("border-thickness")).toInt(), 0, 0});
71 } else {
72 widget->setContentsMargins({});
73 }
74 }
75
76 inline void resumeWidgetEventAndDraw(QWidget *w, QEvent *event)
77 {
78 // Let the widget paint first
79 Private::ObjectHelper::sendEvent(w, event);
80
81 // Due to the timer or user action, Qt will repaint some regions spontaneously,
82 // even if there is no WM_PAINT message, we must wait for it to finish painting
83 // and then update the top border area.
84 ctx->virtual_hook(WindowContext::DrawWindows10BorderHook2, nullptr);
85 }
86
87 inline void resumeWindowEventAndDraw(QWindow *window, QEvent *event)
88 {
89 // Let Qt paint first
90 Private::ObjectHelper::sendEvent(window, event);
91
92 // Upon receiving the WM_PAINT message, Qt will repaint the entire view, and we
93 // must wait for it to finish painting before drawing this top border area.
94 ctx->virtual_hook(WindowContext::DrawWindows10BorderHook2, nullptr);
95 }
96
97 inline void updateExtraMargins(bool windowActive)
98 {
99 if (windowActive) {
100 // Restore margins when the window is active
101 static QVariant defaultMargins = QVariant::fromValue(QMargins(0, 1, 0, 0));
102 ctx->setWindowAttribute(QStringLiteral("extra-margins"), defaultMargins);
103 return;
104 }
105
106 // https://github.com/microsoft/terminal/blob/71a6f26e6ece656084e87de1a528c4a8072eeabd/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp#L904
107 // When the window is inactive, there is a transparency bug in the top
108 // border, and we need to extend the non-client area to the whole title
109 // bar.
110 QRect frame = ctx->windowAttribute(QStringLiteral("window-rect")).toRect();
111 QMargins margins{0, -frame.top(), 0, 0};
112 ctx->setWindowAttribute(QStringLiteral("extra-margins"), QVariant::fromValue(margins));
113 }
114protected:
115 bool nativeEventFilter(const QByteArray &eventType, void *message, QT_NATIVE_EVENT_RESULT_TYPE *result) override
116 {
117 Q_UNUSED(eventType)
118 Q_UNUSED(result)
119
120 const auto msg = static_cast<const MSG *>(message);
121 switch (msg->message) {
122 case WM_DPICHANGED: {
123 updateGeometry();
124 updateExtraMargins(widget->isActiveWindow());
125 break;
126 }
127
128 case WM_ACTIVATE: {
129 updateExtraMargins(LOWORD(msg->wParam) != WA_INACTIVE);
130 break;
131 }
132
133 default:
134 break;
135 }
136 return false;
137 }
138
139 bool sharedEventFilter(QObject *obj, QEvent *event) override
140 {
141 Q_UNUSED(obj)
142
143 auto window = widget->windowHandle();
144
145 // Qt will absolutely send a QExposeEvent or QResizeEvent to the QWindow when it
146 // receives a WM_PAINT message. When the control flow enters the expose handler, Qt
147 // must have already called BeginPaint() and it's the best time for us to draw the
148 // top border.
149
150 // Since a QExposeEvent will be sent immediately after the QResizeEvent, we can simply
151 // ignore it.
152 if (event->type() == QEvent::Expose) {
153#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
154 // Qt 6 drops the region from QExposeEvent entirely, so the check only
155 // makes sense (and is still needed) on Qt 5.
156 auto ee = static_cast<QExposeEvent *>(event);
157 const bool hasRegion = !ee->region().isNull();
158#else
159 const bool hasRegion = true;
160#endif
161 if (window->isExposed() && isNormalWindow() && hasRegion) {
162 resumeWindowEventAndDraw(window, event);
163 return true;
164 }
165 }
166
167 return false;
168 }
169
170 bool eventFilter(QObject *obj, QEvent *event) override
171 {
172 Q_UNUSED(obj)
173
174 switch (event->type()) {
175 case QEvent::UpdateRequest: {
176 if (!isNormalWindow())
177 break;
178 resumeWidgetEventAndDraw(widget, event);
179 return true;
180 }
181
182 case QEvent::WindowStateChange: {
183 updateGeometry();
184 break;
185 }
186
187 case QEvent::WindowActivate:
188 case QEvent::WindowDeactivate: {
189 widget->update();
190 break;
191 }
192
193 default:
194 break;
195 }
196 return false;
197 }
198
199 QWidget *widget;
200 WindowContext *ctx;
201};
202
203void WindowAgentWidgetPrivate::setupWindows10BorderWorkaround()
204{
205 // Install painting hook
206 auto ctx = m_context.get();
207 if (ctx->windowAttribute(QStringLiteral("win10-border-needed")).toBool()) {
208 m_borderHandler = std::make_unique<WidgetBorderHandler>(m_hostWidget, ctx);
209 }
210}
211#endif
212
QColor window
#define QX_WINDOW_END_NAMESPACE
#define QX_WINDOW_BEGIN_NAMESPACE
WindowContext * ctx
#define WM_DPICHANGED