diff --git a/autotests/integration/effects/maximize_animation_test.cpp b/autotests/integration/effects/maximize_animation_test.cpp index b8daa4990e..073035e901 100644 --- a/autotests/integration/effects/maximize_animation_test.cpp +++ b/autotests/integration/effects/maximize_animation_test.cpp @@ -55,6 +55,7 @@ void MaximizeAnimationTest::initTestCase() config->sync(); kwinApp()->setConfig(config); + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", QByteArrayLiteral("1")); kwinApp()->start(); diff --git a/src/effects.cpp b/src/effects.cpp index 637be1f991..5a0515595b 100644 --- a/src/effects.cpp +++ b/src/effects.cpp @@ -293,6 +293,18 @@ void EffectsHandlerImpl::setupWindowConnections(Window *window) connect(window, &Window::windowClosed, this, &EffectsHandlerImpl::slotWindowClosed); connect(window, static_cast(&Window::clientMaximizedStateChanged), this, &EffectsHandlerImpl::slotClientMaximized); + connect(window, static_cast(&Window::clientMaximizedStateAboutToChange), + this, [this](KWin::Window *window, MaximizeMode m) { + if (EffectWindowImpl *w = window->effectWindow()) { + Q_EMIT windowMaximizedStateAboutToChange(w, m & MaximizeHorizontal, m & MaximizeVertical); + } + }); + connect(window, &Window::frameGeometryAboutToChange, + this, [this](KWin::Window *window) { + if (EffectWindowImpl *w = window->effectWindow()) { + Q_EMIT windowFrameGeometryAboutToChange(w); + } + }); connect(window, &Window::clientStartUserMovedResized, this, [this](Window *window) { Q_EMIT windowStartUserMovedResized(window->effectWindow()); }); @@ -357,6 +369,11 @@ void EffectsHandlerImpl::setupUnmanagedConnections(Unmanaged *u) connect(u, &Unmanaged::visibleGeometryChanged, this, [this, u]() { Q_EMIT windowExpandedGeometryChanged(u->effectWindow()); }); + connect(u, &Unmanaged::frameGeometryAboutToChange, this, [this](Window *window) { + if (EffectWindowImpl *w = window->effectWindow()) { + Q_EMIT windowFrameGeometryAboutToChange(w); + } + }); } void EffectsHandlerImpl::reconfigure() @@ -441,6 +458,11 @@ void EffectsHandlerImpl::drawWindow(EffectWindow *w, int mask, const QRegion &re } } +void EffectsHandlerImpl::renderWindow(EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) +{ + m_scene->finalDrawWindow(static_cast(w), mask, region, data); +} + bool EffectsHandlerImpl::hasDecorationShadows() const { return false; diff --git a/src/effects.h b/src/effects.h index b50f6065f9..69b5628c67 100644 --- a/src/effects.h +++ b/src/effects.h @@ -65,6 +65,7 @@ public: Effect *provides(Effect::Feature ef); void drawWindow(EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) override; + void renderWindow(EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) override; void activateWindow(EffectWindow *c) override; EffectWindow *activeWindow() const override; diff --git a/src/effects/blendchanges/blendchanges.cpp b/src/effects/blendchanges/blendchanges.cpp index adb9069c9d..be075c0e3f 100644 --- a/src/effects/blendchanges/blendchanges.cpp +++ b/src/effects/blendchanges/blendchanges.cpp @@ -16,14 +16,13 @@ namespace KWin { BlendChanges::BlendChanges() - : OffscreenEffect() + : CrossFadeEffect() { QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/BlendChanges"), QStringLiteral("org.kde.KWin.BlendChanges"), this, QDBusConnection::ExportAllSlots); - setLive(false); m_timeline.setEasingCurve(QEasingCurve::InOutCubic); } @@ -59,26 +58,6 @@ void KWin::BlendChanges::start(int delay) m_state = ShowingCache; } -void BlendChanges::drawWindow(EffectWindow *window, int mask, const QRegion ®ion, WindowPaintData &data) -{ - // draw the new picture underneath at full opacity - if (m_state != ShowingCache) { - Effect::drawWindow(window, mask, region, data); - } - // then the old on top, it works better than changing both alphas with the current blend mode - if (m_state != Off) { - OffscreenEffect::drawWindow(window, mask, region, data); - } -} - -void BlendChanges::apply(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads) -{ - Q_UNUSED(window) - Q_UNUSED(mask) - Q_UNUSED(quads) - data.setOpacity((1.0 - m_timeline.value()) * data.opacity()); -} - bool BlendChanges::isActive() const { return m_state != Off; @@ -98,6 +77,15 @@ void BlendChanges::postPaintScreen() effects->addRepaintFull(); } +void BlendChanges::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) +{ + Q_UNUSED(w) + Q_UNUSED(mask) + Q_UNUSED(region) + data.setCrossFadeProgress(m_timeline.value()); + effects->paintWindow(w, mask, region, data); +} + void BlendChanges::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) { if (m_state == Off) { diff --git a/src/effects/blendchanges/blendchanges.h b/src/effects/blendchanges/blendchanges.h index a55c1ac7db..f0e2039f77 100644 --- a/src/effects/blendchanges/blendchanges.h +++ b/src/effects/blendchanges/blendchanges.h @@ -14,7 +14,7 @@ namespace KWin { -class BlendChanges : public OffscreenEffect +class BlendChanges : public CrossFadeEffect { Q_OBJECT @@ -27,8 +27,8 @@ public: // Effect interface void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override; void postPaintScreen() override; - void drawWindow(EffectWindow *window, int mask, const QRegion ®ion, WindowPaintData &data) override; - void apply(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads) override; + void paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) override; + bool isActive() const override; public Q_SLOTS: diff --git a/src/effects/maximize/package/contents/code/maximize.js b/src/effects/maximize/package/contents/code/maximize.js index 53e52100cc..ebb5163d03 100644 --- a/src/effects/maximize/package/contents/code/maximize.js +++ b/src/effects/maximize/package/contents/code/maximize.js @@ -16,6 +16,8 @@ class MaximizeEffect { effects.windowMaximizedStateChanged.connect( this.onWindowMaximizedStateChanged.bind(this)); effect.animationEnded.connect(this.restoreForceBlurState.bind(this)); + effects.windowMaximizedStateAboutToChange.connect( + this.onWindowMaximizedStateAboutToChange.bind(this)); this.loadConfig(); } @@ -24,6 +26,29 @@ class MaximizeEffect { this.duration = animationTime(250); } + onWindowMaximizedStateAboutToChange(window) { + if (window.maximizeAnimation1) { + cancel(window.maximizeAnimation1); + delete window.maximizeAnimation1; + } + let couldRetarget = false; + if (window.maximizeAnimation2) { + couldRetarget = retarget(window.maximizeAnimation2, 1.0, this.duration); + } + if (!couldRetarget) { + window.maximizeAnimation2 = animate({ + window: window, + duration: this.duration, + animations: [{ + type: Effect.CrossFadePrevious, + to: 1.0, + from: 0.0, + curve: QEasingCurve.OutCubic + }] + }); + } + } + onWindowMaximizedStateChanged(window) { if (!window.oldGeometry) { return; @@ -62,18 +87,6 @@ class MaximizeEffect { curve: QEasingCurve.OutCubic }] }); - if (!window.resize) { - window.maximizeAnimation2 =animate({ - window: window, - duration: this.duration, - animations: [{ - type: Effect.CrossFadePrevious, - to: 1.0, - from: 0.0, - curve: QEasingCurve.OutCubic - }] - }); - } } restoreForceBlurState(window) { diff --git a/src/effects/morphingpopups/package/contents/code/morphingpopups.js b/src/effects/morphingpopups/package/contents/code/morphingpopups.js index 333b7ecf64..0de3d2bb5d 100644 --- a/src/effects/morphingpopups/package/contents/code/morphingpopups.js +++ b/src/effects/morphingpopups/package/contents/code/morphingpopups.js @@ -15,6 +15,28 @@ var morphingEffect = { morphingEffect.duration = animationTime(150); }, + handleFrameGeometryAboutToChange: function (window) { + //only tooltips and notifications + if (!window.tooltip && !window.notification && !window.criticalNotification) { + return; + } + var couldRetarget = false; + if (window.fadeAnimation) { + couldRetarget = retarget(window.fadeAnimation[0], 1.0, morphingEffect.duration); + } + + if (!couldRetarget) { + window.fadeAnimation = animate({ + window: window, + duration: morphingEffect.duration, + animations: [{ + type: Effect.CrossFadePrevious, + to: 1.0, + from: 0.0 + }] + }); + } + }, handleFrameGeometryChanged: function (window, oldGeometry) { //only tooltips and notifications if (!window.tooltip && !window.notification && !window.criticalNotification) { @@ -98,27 +120,11 @@ var morphingEffect = { }); } - - couldRetarget = false; - if (window.fadeAnimation) { - couldRetarget = retarget(window.fadeAnimation[0], 1.0, morphingEffect.duration); - } - - if (!couldRetarget) { - window.fadeAnimation = animate({ - window: window, - duration: morphingEffect.duration, - animations: [{ - type: Effect.CrossFadePrevious, - to: 1.0, - from: 0.0 - }] - }); - } }, init: function () { effect.configChanged.connect(morphingEffect.loadConfig); + effects.windowFrameGeometryAboutToChange.connect(morphingEffect.handleFrameGeometryAboutToChange); effects.windowFrameGeometryChanged.connect(morphingEffect.handleFrameGeometryChanged); } }; diff --git a/src/events.cpp b/src/events.cpp index 1b64c67f20..495193329d 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -1312,6 +1312,8 @@ void Unmanaged::configureNotifyEvent(xcb_configure_notify_event_t *e) } QRectF newgeom(Xcb::fromXNative(e->x), Xcb::fromXNative(e->y), Xcb::fromXNative(e->width), Xcb::fromXNative(e->height)); if (newgeom != m_frameGeometry) { + Q_EMIT frameGeometryAboutToChange(this); + QRectF old = m_frameGeometry; m_clientGeometry = newgeom; m_frameGeometry = newgeom; diff --git a/src/internalwindow.cpp b/src/internalwindow.cpp index cc756f4033..555156fab8 100644 --- a/src/internalwindow.cpp +++ b/src/internalwindow.cpp @@ -486,6 +486,8 @@ void InternalWindow::commitGeometry(const QRectF &rect) const QRectF oldFrameGeometry = m_frameGeometry; const Output *oldOutput = m_output; + Q_EMIT frameGeometryAboutToChange(this); + m_clientGeometry = frameRectToClientRect(rect); m_frameGeometry = rect; m_bufferGeometry = m_clientGeometry; diff --git a/src/libkwineffects/kwinanimationeffect.cpp b/src/libkwineffects/kwinanimationeffect.cpp index 409740a959..6c78ea13b9 100644 --- a/src/libkwineffects/kwinanimationeffect.cpp +++ b/src/libkwineffects/kwinanimationeffect.cpp @@ -46,7 +46,8 @@ public: quint64 AnimationEffectPrivate::m_animCounter = 0; AnimationEffect::AnimationEffect() - : d_ptr(new AnimationEffectPrivate()) + : CrossFadeEffect() + , d_ptr(new AnimationEffectPrivate()) { if (!s_clock.isValid()) { s_clock.start(); @@ -236,7 +237,7 @@ quint64 AnimationEffect::p_animate(EffectWindow *w, Attribute a, uint meta, int PreviousWindowPixmapLockPtr previousPixmap; if (a == CrossFadePrevious) { - previousPixmap = PreviousWindowPixmapLockPtr::create(w); + CrossFadeEffect::redirect(w); } it->first.append(AniData( @@ -282,7 +283,7 @@ quint64 AnimationEffect::p_animate(EffectWindow *w, Attribute a, uint meta, int triggerRepaint(); } if (shader) { - OffscreenEffect::redirect(w); + CrossFadeEffect::redirect(w); } return ret_id; } @@ -308,6 +309,9 @@ bool AnimationEffect::retarget(quint64 animationId, FPx2 newTarget, int newRemai anim->timeLine.setDuration(std::chrono::milliseconds(newRemainingTime)); anim->timeLine.reset(); + if (anim->attribute == CrossFadePrevious) { + CrossFadeEffect::redirect(entry.key()); + } return true; } } @@ -343,7 +347,6 @@ bool AnimationEffect::freezeInTime(quint64 animationId, qint64 frozenTime) bool AnimationEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags) { Q_D(AnimationEffect); - if (animationId == d->m_justEndedAnimation) { return false; } @@ -393,6 +396,7 @@ bool AnimationEffect::complete(quint64 animationId) } animIt->timeLine.setElapsed(animIt->timeLine.duration()); + unredirect(entryIt.key()); return true; } @@ -532,6 +536,8 @@ void AnimationEffect::paintWindow(EffectWindow *w, int mask, QRegion region, Win { Q_D(AnimationEffect); AniMap::const_iterator entry = d->m_animations.constFind(w); + auto finalRegion = region; + if (entry != d->m_animations.constEnd()) { for (QList::const_iterator anim = entry->first.constBegin(); anim != entry->first.constEnd(); ++anim) { @@ -571,7 +577,7 @@ void AnimationEffect::paintWindow(EffectWindow *w, int mask, QRegion region, Win break; } case Clip: - region = clipRect(w->expandedGeometry().toAlignedRect(), *anim); + finalRegion = clipRect(w->expandedGeometry().toAlignedRect(), *anim); break; case Translation: data += QPointF(interpolated(*anim, 0), interpolated(*anim, 1)); @@ -676,6 +682,7 @@ void AnimationEffect::postPaintScreen() if (anim->shader && std::none_of(entry->first.begin(), entry->first.end(), [anim] (const auto &other) { return anim->id != other.id && other.shader; })) { unredirect(window); } + unredirect(window); animationEnded(window, anim->attribute, anim->meta); d->m_justEndedAnimation = 0; // NOTICE animationEnded is an external call and might have called "::animate" diff --git a/src/libkwineffects/kwinanimationeffect.h b/src/libkwineffects/kwinanimationeffect.h index 4fb552f486..97c9e6b5ae 100644 --- a/src/libkwineffects/kwinanimationeffect.h +++ b/src/libkwineffects/kwinanimationeffect.h @@ -191,7 +191,7 @@ class AnimationEffectPrivate; * * @since 4.8 */ -class KWINEFFECTS_EXPORT AnimationEffect : public OffscreenEffect +class KWINEFFECTS_EXPORT AnimationEffect : public CrossFadeEffect { Q_OBJECT diff --git a/src/libkwineffects/kwineffects.h b/src/libkwineffects/kwineffects.h index 8af8a93529..ec93e309bc 100644 --- a/src/libkwineffects/kwineffects.h +++ b/src/libkwineffects/kwineffects.h @@ -829,6 +829,7 @@ public: virtual void paintWindow(EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) = 0; virtual void postPaintWindow(EffectWindow *w) = 0; virtual void drawWindow(EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) = 0; + virtual void renderWindow(EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) = 0; virtual QVariant kwinOption(KWinOption kwopt) = 0; /** * Sets the cursor while the mouse is intercepted. @@ -1594,6 +1595,24 @@ Q_SIGNALS: * @since 4.7 */ void windowMaximizedStateChanged(KWin::EffectWindow *w, bool horizontal, bool vertical); + + /** + * Signal emitted when the maximized state of the window @p w is about to change, + * but before windowMaximizedStateChanged is emitted or any geometry change. + * Useful for OffscreenEffect to grab a window image before any actual change happens + * + * A window can be in one of four states: + * @li restored: both @p horizontal and @p vertical are @c false + * @li horizontally maximized: @p horizontal is @c true and @p vertical is @c false + * @li vertically maximized: @p horizontal is @c false and @p vertical is @c true + * @li completely maximized: both @p horizontal and @p vertical are @c true + * @param w The window whose maximized state changed + * @param horizontal If @c true maximized horizontally + * @param vertical If @c true maximized vertically + * @since 5.26 + */ + void windowMaximizedStateAboutToChange(KWin::EffectWindow *w, bool horizontal, bool vertical); + /** * Signal emitted when the geometry or shape of a window changed. * This is caused if the window changes geometry without user interaction. @@ -1612,6 +1631,16 @@ Q_SIGNALS: * @since 5.19 */ void windowFrameGeometryChanged(KWin::EffectWindow *window, const QRectF &oldGeometry); + + /** + * This signal is emitted when the frame geometry is about to change, the new one is not known yet. + * Useful for OffscreenEffect to grab a window image before any actual change happens. + * + * @param window The window whose geometry is about to change + * @since 5.26 + */ + void windowFrameGeometryAboutToChange(KWin::EffectWindow *window); + /** * Signal emitted when the windows opacity is changed. * @param w The window whose opacity level is changed. diff --git a/src/libkwineffects/kwinoffscreeneffect.cpp b/src/libkwineffects/kwinoffscreeneffect.cpp index 412b287767..6ee3092dc0 100644 --- a/src/libkwineffects/kwinoffscreeneffect.cpp +++ b/src/libkwineffects/kwinoffscreeneffect.cpp @@ -13,10 +13,21 @@ namespace KWin struct OffscreenData { - std::unique_ptr texture; - std::unique_ptr fbo; - bool isDirty = true; - GLShader *shader = nullptr; +public: + virtual ~OffscreenData(); + void setDirty(); + void setShader(GLShader *newShader); + + void paint(EffectWindow *window, const QRegion ®ion, + const WindowPaintData &data, const WindowQuadList &quads); + + void maybeRender(EffectWindow *window); + +private: + std::unique_ptr m_texture; + std::unique_ptr m_fbo; + bool m_isDirty = true; + GLShader *m_shader = nullptr; }; class OffscreenEffectPrivate @@ -25,12 +36,6 @@ public: QHash windows; QMetaObject::Connection windowDamagedConnection; QMetaObject::Connection windowDeletedConnection; - - void paint(EffectWindow *window, GLTexture *texture, const QRegion ®ion, - const WindowPaintData &data, const WindowQuadList &quads, GLShader *offscreenShader); - - GLTexture *maybeRender(EffectWindow *window, OffscreenData *offscreenData); - bool live = true; }; OffscreenEffect::OffscreenEffect(QObject *parent) @@ -49,12 +54,6 @@ bool OffscreenEffect::supported() return effects->isOpenGLCompositing(); } -void OffscreenEffect::setLive(bool live) -{ - Q_ASSERT(d->windows.isEmpty()); - d->live = live; -} - void OffscreenEffect::redirect(EffectWindow *window) { OffscreenData *&offscreenData = d->windows[window]; @@ -66,11 +65,6 @@ void OffscreenEffect::redirect(EffectWindow *window) if (d->windows.count() == 1) { setupConnections(); } - - if (!d->live) { - effects->makeOpenGLContextCurrent(); - d->maybeRender(window, offscreenData); - } } void OffscreenEffect::unredirect(EffectWindow *window) @@ -89,7 +83,7 @@ void OffscreenEffect::apply(EffectWindow *window, int mask, WindowPaintData &dat Q_UNUSED(quads) } -GLTexture *OffscreenEffectPrivate::maybeRender(EffectWindow *window, OffscreenData *offscreenData) +void OffscreenData::maybeRender(EffectWindow *window) { const QRect geometry = window->expandedGeometry().toAlignedRect(); QSize textureSize = geometry.size(); @@ -98,16 +92,16 @@ GLTexture *OffscreenEffectPrivate::maybeRender(EffectWindow *window, OffscreenDa textureSize *= screen->devicePixelRatio(); } - if (!offscreenData->texture || offscreenData->texture->size() != textureSize) { - offscreenData->texture.reset(new GLTexture(GL_RGBA8, textureSize)); - offscreenData->texture->setFilter(GL_LINEAR); - offscreenData->texture->setWrapMode(GL_CLAMP_TO_EDGE); - offscreenData->fbo.reset(new GLFramebuffer(offscreenData->texture.get())); - offscreenData->isDirty = true; + if (!m_texture || m_texture->size() != textureSize) { + m_texture.reset(new GLTexture(GL_RGBA8, textureSize)); + m_texture->setFilter(GL_LINEAR); + m_texture->setWrapMode(GL_CLAMP_TO_EDGE); + m_fbo.reset(new GLFramebuffer(m_texture.get())); + m_isDirty = true; } - if (offscreenData->isDirty) { - GLFramebuffer::pushFramebuffer(offscreenData->fbo.get()); + if (m_isDirty) { + GLFramebuffer::pushFramebuffer(m_fbo.get()); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); @@ -121,19 +115,31 @@ GLTexture *OffscreenEffectPrivate::maybeRender(EffectWindow *window, OffscreenDa data.setProjectionMatrix(projectionMatrix); const int mask = Effect::PAINT_WINDOW_TRANSFORMED | Effect::PAINT_WINDOW_TRANSLUCENT; - effects->drawWindow(window, mask, infiniteRegion(), data); + effects->renderWindow(window, mask, infiniteRegion(), data); GLFramebuffer::popFramebuffer(); - offscreenData->isDirty = false; + m_isDirty = false; } - - return offscreenData->texture.get(); } -void OffscreenEffectPrivate::paint(EffectWindow *window, GLTexture *texture, const QRegion ®ion, - const WindowPaintData &data, const WindowQuadList &quads, GLShader *offscreenShader) +OffscreenData::~OffscreenData() { - GLShader *shader = offscreenShader ? offscreenShader : ShaderManager::instance()->shader(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::AdjustSaturation); +} + +void OffscreenData::setDirty() +{ + m_isDirty = true; +} + +void OffscreenData::setShader(GLShader *newShader) +{ + m_shader = newShader; +} + +void OffscreenData::paint(EffectWindow *window, const QRegion ®ion, + const WindowPaintData &data, const WindowQuadList &quads) +{ + GLShader *shader = m_shader ? m_shader : ShaderManager::instance()->shader(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::AdjustSaturation); ShaderBinder binder(shader); const bool indexedQuads = GLVertexBuffer::supportsIndexedQuads(); @@ -151,7 +157,7 @@ void OffscreenEffectPrivate::paint(EffectWindow *window, GLTexture *texture, con const size_t size = verticesPerQuad * quads.count() * sizeof(GLVertex2D); GLVertex2D *map = static_cast(vbo->map(size)); - quads.makeInterleavedArrays(primitiveType, map, texture->matrix(NormalizedCoordinates)); + quads.makeInterleavedArrays(primitiveType, map, m_texture->matrix(NormalizedCoordinates)); vbo->unmap(); vbo->bindArrays(); @@ -164,8 +170,8 @@ void OffscreenEffectPrivate::paint(EffectWindow *window, GLTexture *texture, con shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp * data.toMatrix()); shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a)); shader->setUniform(GLShader::Saturation, data.saturation()); - shader->setUniform(GLShader::TextureWidth, texture->width()); - shader->setUniform(GLShader::TextureHeight, texture->height()); + shader->setUniform(GLShader::TextureWidth, m_texture->width()); + shader->setUniform(GLShader::TextureHeight, m_texture->height()); const bool clipping = region != infiniteRegion(); const QRegion clipRegion = clipping ? effects->mapToRenderTarget(region) : infiniteRegion(); @@ -177,9 +183,9 @@ void OffscreenEffectPrivate::paint(EffectWindow *window, GLTexture *texture, con glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - texture->bind(); + m_texture->bind(); vbo->draw(clipRegion, primitiveType, 0, verticesPerQuad * quads.count(), clipping); - texture->unbind(); + m_texture->unbind(); glDisable(GL_BLEND); if (clipping) { @@ -211,15 +217,15 @@ void OffscreenEffect::drawWindow(EffectWindow *window, int mask, const QRegion & quads.append(quad); apply(window, mask, data, quads); - GLTexture *texture = d->maybeRender(window, offscreenData); - d->paint(window, texture, region, data, quads, offscreenData->shader); + offscreenData->maybeRender(window); + offscreenData->paint(window, region, data, quads); } void OffscreenEffect::handleWindowDamaged(EffectWindow *window) { OffscreenData *offscreenData = d->windows.value(window); if (offscreenData) { - offscreenData->isDirty = true; + offscreenData->setDirty(); } } @@ -230,10 +236,9 @@ void OffscreenEffect::handleWindowDeleted(EffectWindow *window) void OffscreenEffect::setupConnections() { - if (d->live) { - d->windowDamagedConnection = - connect(effects, &EffectsHandler::windowDamaged, this, &OffscreenEffect::handleWindowDamaged); - } + d->windowDamagedConnection = + connect(effects, &EffectsHandler::windowDamaged, this, &OffscreenEffect::handleWindowDamaged); + d->windowDeletedConnection = connect(effects, &EffectsHandler::windowDeleted, this, &OffscreenEffect::handleWindowDeleted); } @@ -247,11 +252,111 @@ void OffscreenEffect::destroyConnections() d->windowDeletedConnection = {}; } -void OffscreenEffect::setShader(EffectWindow *window, GLShader *shader) +class CrossFadeWindowData : public OffscreenData { - OffscreenData *offscreenData = d->windows.value(window); +public: + QRectF frameGeometryAtCapture; +}; + +class CrossFadeEffectPrivate +{ +public: + QHash windows; + qreal progress; +}; + +CrossFadeEffect::CrossFadeEffect(QObject *parent) + : Effect(parent) + , d(new CrossFadeEffectPrivate) +{ +} + +CrossFadeEffect::~CrossFadeEffect() +{ + qDeleteAll(d->windows); +} + +void CrossFadeEffect::drawWindow(EffectWindow *window, int mask, const QRegion ®ion, WindowPaintData &data) +{ + Q_UNUSED(mask) + + // paint the new window (if applicable) underneath + Effect::drawWindow(window, mask, region, data); + + // paint old snapshot on top + WindowPaintData previousWindowData = data; + previousWindowData.setOpacity((1.0 - data.crossFadeProgress()) * data.opacity()); + CrossFadeWindowData *offscreenData = d->windows[window]; + if (!offscreenData) { + return; + } + const QRectF expandedGeometry = window->expandedGeometry(); + const QRectF frameGeometry = window->frameGeometry(); + + // This is for the case of *non* live effect, when the window buffer we saved has a different size + // compared to the size the window has now. The "old" window will be rendered scaled to the current + // window geometry, but everything will be scaled, also the shadow if there is any, making the window + // frame not line up anymore with window->frameGeometry() + // to fix that, we consider how much the shadow will have scaled, and use that as margins to the + // current frame geometry. this causes the scaled window to visually line up perfectly with frameGeometry, + // having the scaled shadow all outside of it. + const qreal widthRatio = offscreenData->frameGeometryAtCapture.width() / frameGeometry.width(); + const qreal heightRatio = offscreenData->frameGeometryAtCapture.height() / frameGeometry.height(); + + const QMarginsF margins( + (expandedGeometry.x() - frameGeometry.x()) / widthRatio, + (expandedGeometry.y() - frameGeometry.y()) / heightRatio, + (frameGeometry.right() - expandedGeometry.right()) / widthRatio, + (frameGeometry.bottom() - expandedGeometry.bottom()) / heightRatio); + + QRectF visibleRect = QRectF(QPointF(0, 0), frameGeometry.size()) - margins; + + WindowQuad quad; + quad[0] = WindowVertex(visibleRect.topLeft(), QPointF(0, 0)); + quad[1] = WindowVertex(visibleRect.topRight(), QPointF(1, 0)); + quad[2] = WindowVertex(visibleRect.bottomRight(), QPointF(1, 1)); + quad[3] = WindowVertex(visibleRect.bottomLeft(), QPointF(0, 1)); + + WindowQuadList quads; + quads.append(quad); + offscreenData->paint(window, region, previousWindowData, quads); +} + +void CrossFadeEffect::redirect(EffectWindow *window) +{ + if (d->windows.isEmpty()) { + connect(effects, &EffectsHandler::windowDeleted, this, &CrossFadeEffect::handleWindowDeleted); + } + + CrossFadeWindowData *&offscreenData = d->windows[window]; if (offscreenData) { - offscreenData->shader = shader; + return; + } + offscreenData = new CrossFadeWindowData; + + effects->makeOpenGLContextCurrent(); + offscreenData->maybeRender(window); + offscreenData->frameGeometryAtCapture = window->frameGeometry(); +} + +void CrossFadeEffect::unredirect(EffectWindow *window) +{ + delete d->windows.take(window); + if (d->windows.isEmpty()) { + disconnect(effects, &EffectsHandler::windowDeleted, this, &CrossFadeEffect::handleWindowDeleted); + } +} + +void CrossFadeEffect::handleWindowDeleted(EffectWindow *window) +{ + unredirect(window); +} + +void CrossFadeEffect::setShader(EffectWindow *window, GLShader *shader) +{ + CrossFadeWindowData *offscreenData = d->windows.value(window); + if (offscreenData) { + offscreenData->setShader(shader); } } diff --git a/src/libkwineffects/kwinoffscreeneffect.h b/src/libkwineffects/kwinoffscreeneffect.h index ebf5c29b5f..fba5b787d5 100644 --- a/src/libkwineffects/kwinoffscreeneffect.h +++ b/src/libkwineffects/kwinoffscreeneffect.h @@ -12,6 +12,7 @@ namespace KWin { class OffscreenEffectPrivate; +class CrossFadeEffectPrivate; /** * The OffscreenEffect class is the base class for effects that paint deformed windows. @@ -36,13 +37,6 @@ public: static bool supported(); - /** - * If set our offscreen texture will be updated with the latest contents - * It should be set before redirecting windows - * The default is true - */ - void setLive(bool live); - protected: void drawWindow(EffectWindow *window, int mask, const QRegion ®ion, WindowPaintData &data) override; @@ -62,12 +56,6 @@ protected: */ virtual void apply(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads); - /** - * Allows to specify a @p shader to draw the redirected texture for @p window. - * Can only be called once the window is redirected. - * @since 5.25 - **/ - void setShader(EffectWindow *window, GLShader *shader); private Q_SLOTS: void handleWindowDamaged(EffectWindow *window); @@ -80,4 +68,49 @@ private: std::unique_ptr d; }; +/** + * The CrossFadeEffect class is the base class for effects that paints crossfades + * + * Windows are snapshotted at the time we want to start crossfading from. Hereafter we draw both the new contents + * and the old pixmap at the ratio defined by WindowPaintData::crossFadeProgress + * + * Subclasses are responsible for driving the animation and calling unredirect after animation completes. + * + * If window geometry changes shape after this point our "old" pixmap is resized to fit approximately matching + * frame geometry + */ +class KWINEFFECTS_EXPORT CrossFadeEffect : public Effect +{ + Q_OBJECT +public: + explicit CrossFadeEffect(QObject *parent = nullptr); + ~CrossFadeEffect() override; + + void drawWindow(EffectWindow *window, int mask, const QRegion ®ion, WindowPaintData &data) override; + + /** + * This function must be called when the effect wants to animate the specified + * @a window. + */ + void redirect(EffectWindow *window); + /** + * This function must be called when the effect is done animating the specified + * @a window. The window will be automatically unredirected if it's deleted. + */ + void unredirect(EffectWindow *window); + + /** + * Allows to specify a @p shader to draw the redirected texture for @p window. + * Can only be called once the window is redirected. + * @since 5.25 + **/ + void setShader(EffectWindow *window, GLShader *shader); + + static bool supported(); + +private: + void handleWindowDeleted(EffectWindow *window); + std::unique_ptr d; +}; + } // namespace KWin diff --git a/src/window.cpp b/src/window.cpp index 3d6cc1ba82..79791d3a2b 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1588,6 +1588,7 @@ void Window::maximize(MaximizeMode m) void Window::setMaximize(bool vertically, bool horizontally) { // changeMaximize() flips the state, so change from set->flip + Q_EMIT clientMaximizedStateAboutToChange(this, MaximizeMode((vertically ? MaximizeVertical : 0) | (horizontally ? MaximizeHorizontal : 0))); const MaximizeMode oldMode = requestedMaximizeMode(); changeMaximize( oldMode & MaximizeHorizontal ? !horizontally : horizontally, diff --git a/src/window.h b/src/window.h index f8bd1ccdc3..574f196587 100644 --- a/src/window.h +++ b/src/window.h @@ -1498,6 +1498,11 @@ Q_SIGNALS: */ void clientGeometryChanged(KWin::Window *window, const QRectF &oldGeometry); + /** + * This signal is emitted when the frame geometry is about to change. the new geometry is not known yet + */ + void frameGeometryAboutToChange(KWin::Window *window); + /** * This signal is emitted when the visible geometry has changed. */ @@ -1525,6 +1530,7 @@ Q_SIGNALS: void paletteChanged(const QPalette &p); void colorSchemeChanged(); void captionChanged(); + void clientMaximizedStateAboutToChange(KWin::Window *, MaximizeMode); void clientMaximizedStateChanged(KWin::Window *, MaximizeMode); void clientMaximizedStateChanged(KWin::Window *c, bool h, bool v); void transientChanged(); diff --git a/src/x11window.cpp b/src/x11window.cpp index b0e462343a..160a5c1e59 100644 --- a/src/x11window.cpp +++ b/src/x11window.cpp @@ -4205,6 +4205,7 @@ void X11Window::moveResizeInternal(const QRectF &rect, MoveResizeMode mode) return; } + Q_EMIT frameGeometryAboutToChange(this); const QRectF oldBufferGeometry = m_lastBufferGeometry; const QRectF oldFrameGeometry = m_lastFrameGeometry; const QRectF oldClientGeometry = m_lastClientGeometry; diff --git a/src/xdgshellwindow.cpp b/src/xdgshellwindow.cpp index 3954de6dcf..ca98b3350e 100644 --- a/src/xdgshellwindow.cpp +++ b/src/xdgshellwindow.cpp @@ -296,6 +296,8 @@ void XdgSurfaceWindow::moveResizeInternal(const QRectF &rect, MoveResizeMode mod return; } + Q_EMIT frameGeometryAboutToChange(this); + if (mode != MoveResizeMode::Move) { const QSizeF requestedClientSize = frameSizeToClientSize(rect.size()); if (requestedClientSize == clientSize()) {