Introduce QWindow::paintEvent with QPA plumbing
The explicit paint event on QtGui and QPA level allows us to untangle the expose event, which today has at least 3 different meanings. It also allows us to follow the platform more closely in its semantics of when painting can happen. On some platforms a paint can come in before a window is exposed, e.g. to prepare the first frame. On others a paint can come in after a window has been de-exposed, to save a snapshot of the window for use in an application switcher or similar. The expose keeps its semantics of being a barrier signaling that the application can now render at will, for example in a threaded render loop. There are two compatibility code paths in this patch: 1. For platform plugins that do not yet report the PaintEvents capability, QtGui will synthesize paint events on the platform's behalf, based on the existing expose events coming from the platform. 2. For applications that do not yet implement paintEvent, QtGui will send expose events instead, ensuring the same behavior as before. For now none of the platform plugins deliver paint events natively, so the first compatibility code path is always active. Task-numnber: QTBUG-82676 Change-Id: I0fbe0d4cf451d6a1f07f5eab8d376a6c8a53ce8c Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
This commit is contained in:
parent
c3555fc33d
commit
b5f972361a
@ -420,8 +420,6 @@ QEvent::~QEvent()
|
||||
|
||||
Returns \c true if the event originated outside the application (a
|
||||
system event); otherwise returns \c false.
|
||||
|
||||
The return value of this function is not defined for paint events.
|
||||
*/
|
||||
|
||||
/*!
|
||||
|
@ -1510,12 +1510,16 @@ QMoveEvent::~QMoveEvent()
|
||||
|
||||
\ingroup events
|
||||
|
||||
Expose events are sent to windows when an area of the window is invalidated,
|
||||
for example when window exposure in the windowing system changes.
|
||||
Expose events are sent to windows when they move between the un-exposed and
|
||||
exposed states.
|
||||
|
||||
A Window with a client area that is completely covered by another window, or
|
||||
is otherwise not visible may be considered obscured by Qt and may in such
|
||||
cases not receive expose events.
|
||||
An exposed window is potentially visible to the user. If the window is moved
|
||||
off screen, is made totally obscured by another window, is minimized, or
|
||||
similar, an expose event is sent to the window, and isExposed() might
|
||||
change to false.
|
||||
|
||||
Expose events should not be used to paint. Handle QPaintEvent
|
||||
instead.
|
||||
|
||||
The event handler QWindow::exposeEvent() receives expose events.
|
||||
*/
|
||||
@ -3834,7 +3838,10 @@ QDebug operator<<(QDebug dbg, const QEvent *e)
|
||||
const QEvent::Type type = e->type();
|
||||
switch (type) {
|
||||
case QEvent::Expose:
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
dbg << "QExposeEvent(" << static_cast<const QExposeEvent *>(e)->region() << ')';
|
||||
QT_WARNING_POP
|
||||
break;
|
||||
case QEvent::Paint:
|
||||
dbg << "QPaintEvent(" << static_cast<const QPaintEvent *>(e)->region() << ')';
|
||||
|
@ -551,7 +551,10 @@ public:
|
||||
explicit QExposeEvent(const QRegion &m_region);
|
||||
~QExposeEvent();
|
||||
|
||||
#if QT_DEPRECATED_SINCE(6, 0)
|
||||
QT_DEPRECATED_VERSION_X_6_0("Handle QPaintEvent instead")
|
||||
inline const QRegion ®ion() const { return m_region; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
QRegion m_region;
|
||||
|
@ -2084,6 +2084,9 @@ void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePriv
|
||||
case QWindowSystemInterfacePrivate::Expose:
|
||||
QGuiApplicationPrivate::processExposeEvent(static_cast<QWindowSystemInterfacePrivate::ExposeEvent *>(e));
|
||||
break;
|
||||
case QWindowSystemInterfacePrivate::Paint:
|
||||
QGuiApplicationPrivate::processPaintEvent(static_cast<QWindowSystemInterfacePrivate::PaintEvent *>(e));
|
||||
break;
|
||||
case QWindowSystemInterfacePrivate::Tablet:
|
||||
QGuiApplicationPrivate::processTabletEvent(
|
||||
static_cast<QWindowSystemInterfacePrivate::TabletEvent *>(e));
|
||||
@ -3211,10 +3214,53 @@ void QGuiApplicationPrivate::processExposeEvent(QWindowSystemInterfacePrivate::E
|
||||
p->receivedExpose = true;
|
||||
}
|
||||
|
||||
// If the platform does not send paint events we need to synthesize them from expose events
|
||||
const bool shouldSynthesizePaintEvents = !platformIntegration()->hasCapability(QPlatformIntegration::PaintEvents);
|
||||
|
||||
const bool wasExposed = p->exposed;
|
||||
p->exposed = e->isExposed && window->screen();
|
||||
|
||||
// We treat expose events for an already exposed window as paint events
|
||||
if (wasExposed && p->exposed && shouldSynthesizePaintEvents) {
|
||||
QPaintEvent paintEvent(e->region);
|
||||
QCoreApplication::sendSpontaneousEvent(window, &paintEvent);
|
||||
if (paintEvent.isAccepted())
|
||||
return; // No need to send expose
|
||||
|
||||
// The paint event was not accepted, so we fall through and send an expose
|
||||
// event instead, to maintain compatibility for clients that haven't adopted
|
||||
// paint events yet.
|
||||
}
|
||||
|
||||
QExposeEvent exposeEvent(e->region);
|
||||
QCoreApplication::sendSpontaneousEvent(window, &exposeEvent);
|
||||
e->eventAccepted = exposeEvent.isAccepted();
|
||||
|
||||
// If the window was just exposed we also need to send a paint event,
|
||||
// so that clients that implement paint events will draw something.
|
||||
// Note that we we can not skip this based on the expose event being
|
||||
// accepted, as clients may implement exposeEvent to track the state
|
||||
// change, but without drawing anything.
|
||||
if (!wasExposed && p->exposed && shouldSynthesizePaintEvents) {
|
||||
QPaintEvent paintEvent(e->region);
|
||||
QCoreApplication::sendSpontaneousEvent(window, &paintEvent);
|
||||
}
|
||||
}
|
||||
|
||||
void QGuiApplicationPrivate::processPaintEvent(QWindowSystemInterfacePrivate::PaintEvent *e)
|
||||
{
|
||||
Q_ASSERT_X(platformIntegration()->hasCapability(QPlatformIntegration::PaintEvents), "QGuiApplication",
|
||||
"The platform sent paint events without claiming support for it in QPlatformIntegration::capabilities()");
|
||||
|
||||
if (!e->window)
|
||||
return;
|
||||
|
||||
QPaintEvent paintEvent(e->region);
|
||||
QCoreApplication::sendSpontaneousEvent(e->window, &paintEvent);
|
||||
|
||||
// We report back the accepted state to the platform, so that it can
|
||||
// decide when the best time to send the fallback expose event is.
|
||||
e->eventAccepted = paintEvent.isAccepted();
|
||||
}
|
||||
|
||||
#if QT_CONFIG(draganddrop)
|
||||
|
@ -161,6 +161,7 @@ public:
|
||||
static void processThemeChanged(QWindowSystemInterfacePrivate::ThemeChangeEvent *tce);
|
||||
|
||||
static void processExposeEvent(QWindowSystemInterfacePrivate::ExposeEvent *e);
|
||||
static void processPaintEvent(QWindowSystemInterfacePrivate::PaintEvent *e);
|
||||
|
||||
static void processFileOpenEvent(QWindowSystemInterfacePrivate::FileOpenEvent *e);
|
||||
|
||||
|
@ -777,12 +777,6 @@ void QOpenGLContext::swapBuffers(QSurface *surface)
|
||||
return;
|
||||
}
|
||||
|
||||
if (surface->surfaceClass() == QSurface::Window
|
||||
&& !qt_window_private(static_cast<QWindow *>(surface))->receivedExpose)
|
||||
{
|
||||
qWarning("QOpenGLContext::swapBuffers() called with non-exposed window, behavior is undefined");
|
||||
}
|
||||
|
||||
QPlatformSurface *surfaceHandle = surface->surfaceHandle();
|
||||
if (!surfaceHandle)
|
||||
return;
|
||||
|
@ -178,16 +178,7 @@ int QPaintDeviceWindow::metric(PaintDeviceMetric metric) const
|
||||
*/
|
||||
void QPaintDeviceWindow::exposeEvent(QExposeEvent *exposeEvent)
|
||||
{
|
||||
Q_UNUSED(exposeEvent);
|
||||
Q_D(QPaintDeviceWindow);
|
||||
if (isExposed()) {
|
||||
d->markWindowAsDirty();
|
||||
// Do not rely on exposeEvent->region() as it has some issues for the
|
||||
// time being, namely that it is sometimes in local coordinates,
|
||||
// sometimes relative to the parent, depending on the platform plugin.
|
||||
// We require local coords here.
|
||||
d->doFlush(QRect(QPoint(0, 0), size()));
|
||||
}
|
||||
QWindow::exposeEvent(exposeEvent);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -201,6 +192,15 @@ bool QPaintDeviceWindow::event(QEvent *event)
|
||||
if (handle()) // platform window may be gone when the window is closed during app exit
|
||||
d->handleUpdateEvent();
|
||||
return true;
|
||||
} else if (event->type() == QEvent::Paint) {
|
||||
d->markWindowAsDirty();
|
||||
// Do not rely on exposeEvent->region() as it has some issues for the
|
||||
// time being, namely that it is sometimes in local coordinates,
|
||||
// sometimes relative to the parent, depending on the platform plugin.
|
||||
// We require local coords here.
|
||||
auto region = QRect(QPoint(0, 0), size());
|
||||
d->doFlush(region); // Will end up calling paintEvent
|
||||
return true;
|
||||
}
|
||||
|
||||
return QWindow::event(event);
|
||||
|
@ -66,10 +66,10 @@ public Q_SLOTS:
|
||||
void update();
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent *event);
|
||||
void exposeEvent(QExposeEvent *) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
int metric(PaintDeviceMetric metric) const override;
|
||||
void exposeEvent(QExposeEvent *) override;
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
QPaintDeviceWindow(QPaintDeviceWindowPrivate &dd, QWindow *parent);
|
||||
|
@ -247,6 +247,10 @@ QPlatformServices *QPlatformIntegration::services() const
|
||||
|
||||
\value OpenGLOnRasterSurface The platform supports making a QOpenGLContext current
|
||||
in combination with a QWindow of type RasterSurface.
|
||||
|
||||
\value PaintEvents The platform sends paint events instead of expose events when
|
||||
the window needs repainting. Expose events are only sent when a window is toggled
|
||||
from a non-exposed to exposed state or back.
|
||||
*/
|
||||
|
||||
/*!
|
||||
|
@ -131,7 +131,8 @@ public:
|
||||
SwitchableWidgetComposition,
|
||||
TopStackedNativeChildWindows,
|
||||
OpenGLOnRasterSurface,
|
||||
MaximizeUsingFullscreenGeometry
|
||||
MaximizeUsingFullscreenGeometry,
|
||||
PaintEvents
|
||||
};
|
||||
|
||||
virtual ~QPlatformIntegration() { }
|
||||
|
@ -1198,7 +1198,7 @@ void QWindow::requestActivate()
|
||||
|
||||
When the window is not exposed, it is shown by the application
|
||||
but it is still not showing in the windowing system, so the application
|
||||
should minimize rendering and other graphical activities.
|
||||
should minimize animations and other graphical activities.
|
||||
|
||||
An exposeEvent() is sent every time this value changes.
|
||||
|
||||
@ -2246,30 +2246,47 @@ bool QWindow::close()
|
||||
}
|
||||
|
||||
/*!
|
||||
The expose event (\a ev) is sent by the window system whenever an area of
|
||||
the window is invalidated, for example due to the exposure in the windowing
|
||||
system changing.
|
||||
The expose event (\a ev) is sent by the window system when a window moves
|
||||
between the un-exposed and exposed states.
|
||||
|
||||
The application can start rendering into the window with QBackingStore
|
||||
and QOpenGLContext as soon as it gets an exposeEvent() such that
|
||||
isExposed() is true.
|
||||
An exposed window is potentially visible to the user. If the window is moved
|
||||
off screen, is made totally obscured by another window, is minimized, or
|
||||
similar, this function might be called and the value of isExposed() might
|
||||
change to false. You may use this event to limit expensive operations such
|
||||
as animations to only run when the window is exposed.
|
||||
|
||||
If the window is moved off screen, is made totally obscured by another
|
||||
window, iconified or similar, this function might be called and the
|
||||
value of isExposed() might change to false. When this happens,
|
||||
an application should stop its rendering as it is no longer visible
|
||||
to the user.
|
||||
This event should not be used to paint. To handle painting implement
|
||||
paintEvent() instead.
|
||||
|
||||
A resize event will always be sent before the expose event the first time
|
||||
a window is shown.
|
||||
|
||||
\sa isExposed()
|
||||
\sa paintEvent(), isExposed()
|
||||
*/
|
||||
void QWindow::exposeEvent(QExposeEvent *ev)
|
||||
{
|
||||
ev->ignore();
|
||||
}
|
||||
|
||||
/*!
|
||||
The paint event (\a ev) is sent by the window system whenever an area of
|
||||
the window needs a repaint, for example when initially showing the window,
|
||||
or due to parts of the window being uncovered by moving another window.
|
||||
|
||||
The application is expected to render into the window in response to the
|
||||
paint event, regardless of the exposed state of the window. For example,
|
||||
a paint event may be sent before the window is exposed, to prepare it for
|
||||
showing to the user.
|
||||
|
||||
\since 6.0
|
||||
|
||||
\sa exposeEvent()
|
||||
*/
|
||||
void QWindow::paintEvent(QPaintEvent *ev)
|
||||
{
|
||||
ev->ignore();
|
||||
}
|
||||
|
||||
/*!
|
||||
Override this to handle window move events (\a ev).
|
||||
*/
|
||||
@ -2421,6 +2438,10 @@ bool QWindow::event(QEvent *ev)
|
||||
exposeEvent(static_cast<QExposeEvent *>(ev));
|
||||
break;
|
||||
|
||||
case QEvent::Paint:
|
||||
paintEvent(static_cast<QPaintEvent *>(ev));
|
||||
break;
|
||||
|
||||
case QEvent::Show:
|
||||
showEvent(static_cast<QShowEvent *>(ev));
|
||||
break;
|
||||
|
@ -64,6 +64,7 @@ QT_BEGIN_NAMESPACE
|
||||
class QWindowPrivate;
|
||||
|
||||
class QExposeEvent;
|
||||
class QPaintEvent;
|
||||
class QFocusEvent;
|
||||
class QMoveEvent;
|
||||
class QResizeEvent;
|
||||
@ -346,6 +347,7 @@ Q_SIGNALS:
|
||||
protected:
|
||||
virtual void exposeEvent(QExposeEvent *);
|
||||
virtual void resizeEvent(QResizeEvent *);
|
||||
virtual void paintEvent(QPaintEvent *);
|
||||
virtual void moveEvent(QMoveEvent *);
|
||||
virtual void focusInEvent(QFocusEvent *);
|
||||
virtual void focusOutEvent(QFocusEvent *);
|
||||
|
@ -348,13 +348,21 @@ QWindowSystemInterfacePrivate::ExposeEvent::ExposeEvent(QWindow *window, const Q
|
||||
This is required behavior on platforms where OpenGL swapbuffers stops
|
||||
blocking for obscured windows (like macOS).
|
||||
*/
|
||||
QT_DEFINE_QPA_EVENT_HANDLER(void, handleExposeEvent, QWindow *window, const QRegion ®ion)
|
||||
QT_DEFINE_QPA_EVENT_HANDLER(bool, handleExposeEvent, QWindow *window, const QRegion ®ion)
|
||||
{
|
||||
QWindowSystemInterfacePrivate::ExposeEvent *e =
|
||||
new QWindowSystemInterfacePrivate::ExposeEvent(window, QHighDpi::fromNativeLocalExposedRegion(region, window));
|
||||
QWindowSystemInterfacePrivate::handleWindowSystemEvent<Delivery>(e);
|
||||
return QWindowSystemInterfacePrivate::handleWindowSystemEvent<Delivery>(e);
|
||||
}
|
||||
|
||||
QT_DEFINE_QPA_EVENT_HANDLER(bool, handlePaintEvent, QWindow *window, const QRegion ®ion)
|
||||
{
|
||||
QWindowSystemInterfacePrivate::PaintEvent *e =
|
||||
new QWindowSystemInterfacePrivate::PaintEvent(window, QHighDpi::fromNativeLocalExposedRegion(region, window));
|
||||
return QWindowSystemInterfacePrivate::handleWindowSystemEvent<Delivery>(e);
|
||||
}
|
||||
|
||||
|
||||
QT_DEFINE_QPA_EVENT_HANDLER(bool, handleCloseEvent, QWindow *window)
|
||||
{
|
||||
Q_ASSERT(window);
|
||||
|
@ -190,7 +190,10 @@ public:
|
||||
|
||||
// region is in local coordinates, do not confuse with geometry which is parent-relative
|
||||
template<typename Delivery = QWindowSystemInterface::DefaultDelivery>
|
||||
static void handleExposeEvent(QWindow *window, const QRegion ®ion);
|
||||
static bool handleExposeEvent(QWindow *window, const QRegion ®ion);
|
||||
|
||||
template<typename Delivery = QWindowSystemInterface::DefaultDelivery>
|
||||
static bool handlePaintEvent(QWindow *window, const QRegion ®ion);
|
||||
|
||||
template<typename Delivery = QWindowSystemInterface::DefaultDelivery>
|
||||
static bool handleCloseEvent(QWindow *window);
|
||||
|
@ -101,7 +101,8 @@ public:
|
||||
FlushEvents = 0x20,
|
||||
WindowScreenChanged = 0x21,
|
||||
SafeAreaMarginsChanged = 0x22,
|
||||
ApplicationTermination = 0x23
|
||||
ApplicationTermination = 0x23,
|
||||
Paint = 0x24
|
||||
};
|
||||
|
||||
class WindowSystemEvent {
|
||||
@ -367,6 +368,14 @@ public:
|
||||
QRegion region;
|
||||
};
|
||||
|
||||
class PaintEvent : public WindowSystemEvent {
|
||||
public:
|
||||
PaintEvent(QWindow *window, const QRegion ®ion)
|
||||
: WindowSystemEvent(Paint), window(window), region(region) {}
|
||||
QPointer<QWindow> window;
|
||||
QRegion region;
|
||||
};
|
||||
|
||||
class FileOpenEvent : public WindowSystemEvent {
|
||||
public:
|
||||
FileOpenEvent(const QString& fileName)
|
||||
|
@ -240,13 +240,6 @@ void QBackingStore::flush(const QRegion ®ion, QWindow *window, const QPoint &
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef QBACKINGSTORE_DEBUG
|
||||
if (window && window->isTopLevel() && !qt_window_private(window)->receivedExpose) {
|
||||
qWarning().nospace() << "QBackingStore::flush() called with non-exposed window "
|
||||
<< window << ", behavior is undefined";
|
||||
}
|
||||
#endif
|
||||
|
||||
Q_ASSERT(window == topLevelWindow || topLevelWindow->isAncestorOf(window, QWindow::ExcludeTransients));
|
||||
|
||||
handle()->flush(window, QHighDpi::toNativeLocalRegion(region, window),
|
||||
|
@ -224,6 +224,7 @@ static inline bool shouldBePropagatedToWidget(QEvent *event)
|
||||
case QEvent::DynamicPropertyChange:
|
||||
case QEvent::ChildAdded:
|
||||
case QEvent::ChildRemoved:
|
||||
case QEvent::Paint:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
@ -1009,8 +1010,11 @@ void QWidgetWindow::handleExposeEvent(QExposeEvent *event)
|
||||
m_widget->setAttribute(Qt::WA_Mapped);
|
||||
for (QWidget *p = m_widget->parentWidget(); p && !p->testAttribute(Qt::WA_Mapped); p = p->parentWidget())
|
||||
p->setAttribute(Qt::WA_Mapped);
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
if (!event->region().isNull())
|
||||
wPriv->syncBackingStore(event->region());
|
||||
QT_WARNING_POP
|
||||
} else {
|
||||
m_widget->setAttribute(Qt::WA_Mapped, false);
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ public:
|
||||
bool event(QEvent *event) override
|
||||
{
|
||||
m_received[event->type()]++;
|
||||
return QWindow::event(event);
|
||||
return QRasterWindow::event(event);
|
||||
}
|
||||
|
||||
int received(QEvent::Type type)
|
||||
|
@ -70,7 +70,10 @@ public:
|
||||
|
||||
backingStore.endPaint();
|
||||
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
backingStore.flush(event->region().boundingRect());
|
||||
QT_WARNING_POP
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -55,6 +55,7 @@ private slots:
|
||||
void setVisible();
|
||||
void setVisibleFalseDoesNotCreateWindow();
|
||||
void eventOrderOnShow();
|
||||
void paintEvent();
|
||||
void resizeEventAfterResize();
|
||||
void exposeEventOnShrink_QTBUG54040();
|
||||
void mapGlobal();
|
||||
@ -307,7 +308,10 @@ public:
|
||||
m_order << event->type();
|
||||
switch (event->type()) {
|
||||
case QEvent::Expose:
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
m_exposeRegion = static_cast<QExposeEvent *>(event)->region();
|
||||
QT_WARNING_POP
|
||||
break;
|
||||
|
||||
case QEvent::PlatformSurface:
|
||||
@ -392,6 +396,44 @@ void tst_QWindow::eventOrderOnShow()
|
||||
QVERIFY(window.eventIndex(QEvent::Resize) < window.eventIndex(QEvent::Expose));
|
||||
}
|
||||
|
||||
class PaintWindow : public Window
|
||||
{
|
||||
public:
|
||||
using Window::Window;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) {
|
||||
// Handled, not calling base class
|
||||
}
|
||||
};
|
||||
|
||||
void tst_QWindow::paintEvent()
|
||||
{
|
||||
PaintWindow window;
|
||||
window.setTitle(QLatin1String(QTest::currentTestFunction()));
|
||||
window.setGeometry(QRect(m_availableTopLeft, m_testWindowSize));
|
||||
window.show();
|
||||
|
||||
QTRY_VERIFY(window.received(QEvent::Expose));
|
||||
QTRY_VERIFY(window.received(QEvent::Paint));
|
||||
QVERIFY(window.isExposed());
|
||||
|
||||
// There is no defined order between paint and expose, so we don't test that
|
||||
|
||||
window.reset();
|
||||
window.resize(m_testWindowSize * 2);
|
||||
|
||||
QTRY_VERIFY(window.received(QEvent::Paint));
|
||||
QVERIFY(!window.received(QEvent::Expose));
|
||||
|
||||
window.reset();
|
||||
window.hide();
|
||||
|
||||
QTRY_VERIFY(window.received(QEvent::Expose));
|
||||
QVERIFY(!window.received(QEvent::Paint));
|
||||
QVERIFY(!window.isExposed());
|
||||
}
|
||||
|
||||
void tst_QWindow::resizeEventAfterResize()
|
||||
{
|
||||
// Some platforms enforce minimum widths for windows, which can cause extra resize
|
||||
|
Loading…
Reference in New Issue
Block a user