Add new blend effect
When changing global settings like switching between light and dark themes the result is currently quite messy. Apps update in a scattered order and a jarring change. The solution is heavily inspired from Gnome, we cache the window for X milliseconds, then perform a crossfade between the cache and current contents. It does mean any video is paused for half a second, but pragmatically it's not really bothersome. On receipt of an explicit DBus method call we create a cache and start animating. The explicit method call is needed so that we can guarantee kwin has a fully copy before a client might change. Use of an offscreen texture is used so that we include any window decorations. The DeformEffect effect is repurposed as it has most the relevnat logic if we remove the live updating.
This commit is contained in:
parent
a23d23593b
commit
9de9b93325
8 changed files with 238 additions and 4 deletions
|
@ -86,6 +86,7 @@ add_subdirectory(touchpoints)
|
|||
add_subdirectory(zoom)
|
||||
|
||||
# OpenGL-specific effects
|
||||
add_subdirectory(blendchanges)
|
||||
add_subdirectory(blur)
|
||||
add_subdirectory(backgroundcontrast)
|
||||
add_subdirectory(glide)
|
||||
|
|
14
src/effects/blendchanges/CMakeLists.txt
Normal file
14
src/effects/blendchanges/CMakeLists.txt
Normal file
|
@ -0,0 +1,14 @@
|
|||
#######################################
|
||||
# Effect
|
||||
|
||||
set(blendchanges_SOURCES
|
||||
main.cpp
|
||||
blendchanges.cpp
|
||||
)
|
||||
|
||||
kwin4_add_effect_module(kwin4_effect_blend ${blendchanges_SOURCES})
|
||||
target_link_libraries(kwin4_effect_blend PRIVATE
|
||||
kwineffects
|
||||
kwinglutils
|
||||
Qt::DBus
|
||||
)
|
115
src/effects/blendchanges/blendchanges.cpp
Normal file
115
src/effects/blendchanges/blendchanges.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
SPDX-FileCopyrightText: 2022 David Edmundson <davidedmundson@kde.org>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
// own
|
||||
#include "blendchanges.h"
|
||||
#include "kwinglutils.h"
|
||||
|
||||
#include <QDBusConnection>
|
||||
#include <QTimer>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
BlendChanges::BlendChanges()
|
||||
: DeformEffect()
|
||||
{
|
||||
QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/BlendChanges"),
|
||||
QStringLiteral("org.kde.KWin.BlendChanges"),
|
||||
this,
|
||||
QDBusConnection::ExportAllSlots);
|
||||
|
||||
setLive(false);
|
||||
m_timeline.setEasingCurve(QEasingCurve::InOutCubic);
|
||||
}
|
||||
|
||||
BlendChanges::~BlendChanges() = default;
|
||||
|
||||
bool BlendChanges::supported()
|
||||
{
|
||||
return effects->compositingType() == OpenGLCompositing && effects->animationsSupported();
|
||||
}
|
||||
|
||||
void KWin::BlendChanges::start(int delay)
|
||||
{
|
||||
int animationDuration = animationTime(400);
|
||||
|
||||
if (!supported() || m_state != Off) {
|
||||
return;
|
||||
}
|
||||
const EffectWindowList allWindows = effects->stackingOrder();
|
||||
for (auto window : allWindows) {
|
||||
redirect(window);
|
||||
}
|
||||
|
||||
QTimer::singleShot(delay, this, [this, animationDuration]() {
|
||||
m_timeline.setDuration(std::chrono::milliseconds(animationDuration));
|
||||
effects->addRepaintFull();
|
||||
m_state = Blending;
|
||||
});
|
||||
|
||||
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) {
|
||||
DeformEffect::drawWindow(window, mask, region, data);
|
||||
}
|
||||
}
|
||||
|
||||
void BlendChanges::deform(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads)
|
||||
{
|
||||
Q_UNUSED(window)
|
||||
Q_UNUSED(mask)
|
||||
Q_UNUSED(quads)
|
||||
data.setOpacity(1.0 - m_timeline.value());
|
||||
}
|
||||
|
||||
bool BlendChanges::isActive() const
|
||||
{
|
||||
return m_state != Off;
|
||||
}
|
||||
|
||||
void BlendChanges::postPaintScreen()
|
||||
{
|
||||
if (m_timeline.done()) {
|
||||
m_lastPresentTime = std::chrono::milliseconds::zero();
|
||||
m_timeline.reset();
|
||||
m_state = Off;
|
||||
|
||||
const EffectWindowList allWindows = effects->stackingOrder();
|
||||
for (auto window : allWindows) {
|
||||
unredirect(window);
|
||||
}
|
||||
}
|
||||
effects->addRepaintFull();
|
||||
}
|
||||
|
||||
void BlendChanges::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
|
||||
{
|
||||
if (m_state == Off) {
|
||||
return;
|
||||
}
|
||||
if (m_state == Blending) {
|
||||
std::chrono::milliseconds delta(0);
|
||||
if (m_lastPresentTime.count()) {
|
||||
delta = presentTime - m_lastPresentTime;
|
||||
}
|
||||
m_lastPresentTime = presentTime;
|
||||
m_timeline.update(delta);
|
||||
}
|
||||
|
||||
effects->prePaintScreen(data, presentTime);
|
||||
}
|
||||
|
||||
} // namespace KWin
|
53
src/effects/blendchanges/blendchanges.h
Normal file
53
src/effects/blendchanges/blendchanges.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
SPDX-FileCopyrightText: 2022 David Edmundson <davidedmundson@kde.org>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#pragma once
|
||||
#include <chrono>
|
||||
#include <kwindeformeffect.h>
|
||||
#include <kwineffects.h>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
class BlendChanges : public DeformEffect
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BlendChanges();
|
||||
~BlendChanges() override;
|
||||
|
||||
static bool supported();
|
||||
|
||||
// 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 deform(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads) override;
|
||||
bool isActive() const override;
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* Called from DBus, this should be called before triggering any changes
|
||||
* delay (ms) refers to how long to keep the current frame before starting a crossfade
|
||||
* We should expect all clients to have repainted by the time this expires
|
||||
*/
|
||||
void start(int delay = 300);
|
||||
|
||||
private:
|
||||
TimeLine m_timeline;
|
||||
std::chrono::milliseconds m_lastPresentTime = std::chrono::milliseconds::zero();
|
||||
enum State {
|
||||
Off,
|
||||
ShowingCache,
|
||||
Blending
|
||||
};
|
||||
State m_state = Off;
|
||||
};
|
||||
|
||||
} // namespace KWin
|
18
src/effects/blendchanges/main.cpp
Normal file
18
src/effects/blendchanges/main.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2022 David Edmundson <davidedmundson@kde.org>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "blendchanges.h"
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
KWIN_EFFECT_FACTORY_SUPPORTED(BlendChanges,
|
||||
"metadata.json.stripped",
|
||||
return BlendChanges::supported();)
|
||||
|
||||
} // namespace KWin
|
||||
|
||||
#include "main.moc"
|
13
src/effects/blendchanges/metadata.json
Normal file
13
src/effects/blendchanges/metadata.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"KPlugin": {
|
||||
"Name": "Blend Changes",
|
||||
"Description": "Animates system style changes",
|
||||
"Category": "Appearance",
|
||||
"EnabledByDefault": true,
|
||||
"Id": "blendchanges",
|
||||
"License": "GPL"
|
||||
},
|
||||
"org.kde.kwin.effect": {
|
||||
"internal": true
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ public:
|
|||
const WindowPaintData &data, const WindowQuadList &quads);
|
||||
|
||||
GLTexture *maybeRender(EffectWindow *window, DeformOffscreenData *offscreenData);
|
||||
bool live = true;
|
||||
};
|
||||
|
||||
DeformEffect::DeformEffect(QObject *parent)
|
||||
|
@ -47,6 +48,12 @@ bool DeformEffect::supported()
|
|||
return effects->isOpenGLCompositing();
|
||||
}
|
||||
|
||||
void DeformEffect::setLive(bool live)
|
||||
{
|
||||
Q_ASSERT(d->windows.isEmpty());
|
||||
d->live = live;
|
||||
}
|
||||
|
||||
void DeformEffect::redirect(EffectWindow *window)
|
||||
{
|
||||
DeformOffscreenData *&offscreenData = d->windows[window];
|
||||
|
@ -58,6 +65,11 @@ void DeformEffect::redirect(EffectWindow *window)
|
|||
if (d->windows.count() == 1) {
|
||||
setupConnections();
|
||||
}
|
||||
|
||||
if (!d->live) {
|
||||
effects->makeOpenGLContextCurrent();
|
||||
d->maybeRender(window, offscreenData);
|
||||
}
|
||||
}
|
||||
|
||||
void DeformEffect::unredirect(EffectWindow *window)
|
||||
|
@ -205,8 +217,10 @@ void DeformEffect::handleWindowDeleted(EffectWindow *window)
|
|||
|
||||
void DeformEffect::setupConnections()
|
||||
{
|
||||
if (d->live) {
|
||||
d->windowDamagedConnection =
|
||||
connect(effects, &EffectsHandler::windowDamaged, this, &DeformEffect::handleWindowDamaged);
|
||||
}
|
||||
d->windowDeletedConnection =
|
||||
connect(effects, &EffectsHandler::windowDeleted, this, &DeformEffect::handleWindowDeleted);
|
||||
}
|
||||
|
|
|
@ -37,10 +37,16 @@ public:
|
|||
|
||||
static bool supported();
|
||||
|
||||
private:
|
||||
void drawWindow(EffectWindow *window, int mask, const QRegion ®ion, WindowPaintData &data) override;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This function must be called when the effect wants to animate the specified
|
||||
* @a window.
|
||||
|
|
Loading…
Reference in a new issue