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:
parent
b7f950a5a6
commit
31d1f885ce
18 changed files with 344 additions and 125 deletions
|
@ -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();
|
||||
|
|
|
@ -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 ®ion, WindowPaintData &data)
|
||||
{
|
||||
m_scene->finalDrawWindow(static_cast<EffectWindowImpl *>(w), mask, region, data);
|
||||
}
|
||||
|
||||
bool EffectsHandlerImpl::hasDecorationShadows() const
|
||||
{
|
||||
return false;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -191,7 +191,7 @@ class AnimationEffectPrivate;
|
|||
*
|
||||
* @since 4.8
|
||||
*/
|
||||
class KWINEFFECTS_EXPORT AnimationEffect : public OffscreenEffect
|
||||
class KWINEFFECTS_EXPORT AnimationEffect : public CrossFadeEffect
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 ®ion,
|
||||
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 ®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<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 ®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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<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 ®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<CrossFadeEffectPrivate> d;
|
||||
};
|
||||
|
||||
} // namespace KWin
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
Loading…
Reference in a new issue