Restore the crossfade effect

This enables again the crossfade between the old window picture and the new one in the maximize and morphingpopup effects.
It does that with the OffScreenEffect redirect() feature.

BUG:439689
BUG:435423
This commit is contained in:
Marco Martin 2022-09-07 10:16:36 +00:00
parent b7f950a5a6
commit 31d1f885ce
18 changed files with 344 additions and 125 deletions

View file

@ -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();

View file

@ -293,6 +293,18 @@ void EffectsHandlerImpl::setupWindowConnections(Window *window)
connect(window, &Window::windowClosed, this, &EffectsHandlerImpl::slotWindowClosed);
connect(window, static_cast<void (Window::*)(KWin::Window *, MaximizeMode)>(&Window::clientMaximizedStateChanged),
this, &EffectsHandlerImpl::slotClientMaximized);
connect(window, static_cast<void (Window::*)(KWin::Window *, MaximizeMode)>(&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 &region, WindowPaintData &data)
{
m_scene->finalDrawWindow(static_cast<EffectWindowImpl *>(w), mask, region, data);
}
bool EffectsHandlerImpl::hasDecorationShadows() const
{
return false;

View file

@ -65,6 +65,7 @@ public:
Effect *provides(Effect::Feature ef);
void drawWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data) override;
void renderWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data) override;
void activateWindow(EffectWindow *c) override;
EffectWindow *activeWindow() const override;

View file

@ -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 &region, 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) {

View file

@ -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 &region, 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:

View file

@ -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) {

View file

@ -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);
}
};

View file

@ -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;

View file

@ -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;

View file

@ -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<AniData>::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"

View file

@ -191,7 +191,7 @@ class AnimationEffectPrivate;
*
* @since 4.8
*/
class KWINEFFECTS_EXPORT AnimationEffect : public OffscreenEffect
class KWINEFFECTS_EXPORT AnimationEffect : public CrossFadeEffect
{
Q_OBJECT

View file

@ -829,6 +829,7 @@ public:
virtual void paintWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data) = 0;
virtual void postPaintWindow(EffectWindow *w) = 0;
virtual void drawWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data) = 0;
virtual void renderWindow(EffectWindow *w, int mask, const QRegion &region, 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.

View file

@ -13,10 +13,21 @@ namespace KWin
struct OffscreenData
{
std::unique_ptr<GLTexture> texture;
std::unique_ptr<GLFramebuffer> fbo;
bool isDirty = true;
GLShader *shader = nullptr;
public:
virtual ~OffscreenData();
void setDirty();
void setShader(GLShader *newShader);
void paint(EffectWindow *window, const QRegion &region,
const WindowPaintData &data, const WindowQuadList &quads);
void maybeRender(EffectWindow *window);
private:
std::unique_ptr<GLTexture> m_texture;
std::unique_ptr<GLFramebuffer> m_fbo;
bool m_isDirty = true;
GLShader *m_shader = nullptr;
};
class OffscreenEffectPrivate
@ -25,12 +36,6 @@ public:
QHash<EffectWindow *, OffscreenData *> windows;
QMetaObject::Connection windowDamagedConnection;
QMetaObject::Connection windowDeletedConnection;
void paint(EffectWindow *window, GLTexture *texture, const QRegion &region,
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 &region,
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 &region,
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<GLVertex2D *>(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<EffectWindow *, CrossFadeWindowData *> 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 &region, 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);
}
}

View file

@ -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 &region, 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<OffscreenEffectPrivate> 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 &region, 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<CrossFadeEffectPrivate> d;
};
} // namespace KWin

View file

@ -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,

View file

@ -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();

View file

@ -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;

View file

@ -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()) {