2011-10-19 23:04:52 +00:00
|
|
|
/********************************************************************
|
|
|
|
KWin - the KDE window manager
|
|
|
|
This file is part of the KDE project.
|
|
|
|
|
|
|
|
Copyright (C) 2011 Thomas Lübking <thomas.luebking@web.de>
|
[scripting] Introduce redirect function
Summary:
Consider current implementation of the Squash effect: if a window was
minimized, an animation will be started; if the window is unminimized
and the animation is still active (that can happen when user clicks on
app's icon really fast), the animation will be stopped and a new one will
be created. Such behavior can lead to rapid jumps in the observed
"animation".
A better approach would be first try to **reverse** the already active
animation, and if that attempt wasn't successful, start a new animation.
This patch introduces a new function to the scripted effects API that
lets JavaScript effects to control direction of animations. The
prototype of the function looks as follows:
redirect(<animation id(s)>, <direction>, [<termination policy>])
the first argument is an animation id or a list of animation ids, the
second argument specifies the new direction of the animation or
animations if a list of ids was passed as the first argument. The
third argument specifies whether the animation(s) should be terminated
when it(they) reaches the source position, currently it's relevant only
for animations that are created with set() function. The termination
policy argument is optional, by default it's Effect.TerminateAtSource.
We can use this function to fix issues with rapid jumps in the Squash
effect. Also, redirect() lets us to write effects for simple animations
in slightly different style: first, we have to start the main animation
(e.g. for the Dialog Parent effect, it would be dimming of main windows)
and then change direction of the animation depending on external events,
e.g. when the Desktop Cube effect is activated.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, abetts, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D16449
2018-10-24 19:58:29 +00:00
|
|
|
Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
|
2011-10-19 23:04:52 +00:00
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*********************************************************************/
|
|
|
|
|
|
|
|
#include "kwinanimationeffect.h"
|
|
|
|
#include "anidata_p.h"
|
|
|
|
|
2012-11-03 20:33:34 +00:00
|
|
|
#include <QDateTime>
|
2011-10-19 23:04:52 +00:00
|
|
|
#include <QTimer>
|
2012-10-27 21:35:19 +00:00
|
|
|
#include <QtDebug>
|
2013-02-26 07:02:27 +00:00
|
|
|
#include <QVector3D>
|
2011-10-19 23:04:52 +00:00
|
|
|
|
2012-10-27 21:35:19 +00:00
|
|
|
QDebug operator<<(QDebug dbg, const KWin::FPx2 &fpx2)
|
|
|
|
{
|
2013-07-23 05:02:52 +00:00
|
|
|
dbg.nospace() << fpx2[0] << "," << fpx2[1] << QString(fpx2.isValid() ? QStringLiteral(" (valid)") : QStringLiteral(" (invalid)"));
|
2012-10-27 21:35:19 +00:00
|
|
|
return dbg.space();
|
|
|
|
}
|
|
|
|
|
2011-10-19 23:04:52 +00:00
|
|
|
namespace KWin {
|
2012-11-03 20:33:34 +00:00
|
|
|
|
|
|
|
QElapsedTimer AnimationEffect::s_clock;
|
|
|
|
|
2013-09-13 14:46:02 +00:00
|
|
|
class AnimationEffectPrivate {
|
2011-10-19 23:04:52 +00:00
|
|
|
public:
|
2016-02-03 11:53:37 +00:00
|
|
|
AnimationEffectPrivate()
|
|
|
|
{
|
|
|
|
m_animated = m_damageDirty = m_animationsTouched = m_isInitialized = false;
|
|
|
|
m_justEndedAnimation = 0;
|
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
AnimationEffect::AniMap m_animations;
|
2016-03-04 17:17:42 +00:00
|
|
|
static quint64 m_animCounter;
|
2018-10-03 00:11:59 +00:00
|
|
|
quint64 m_justEndedAnimation; // protect against cancel
|
|
|
|
QWeakPointer<FullScreenEffectLock> m_fullScreenEffectLock;
|
|
|
|
bool m_animated, m_damageDirty, m_needSceneRepaint, m_animationsTouched, m_isInitialized;
|
2011-10-19 23:04:52 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
using namespace KWin;
|
|
|
|
|
2016-03-04 17:17:42 +00:00
|
|
|
quint64 AnimationEffectPrivate::m_animCounter = 0;
|
|
|
|
|
2011-10-19 23:04:52 +00:00
|
|
|
AnimationEffect::AnimationEffect() : d_ptr(new AnimationEffectPrivate())
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
|
|
|
d->m_animated = false;
|
2012-11-03 20:33:34 +00:00
|
|
|
if (!s_clock.isValid())
|
|
|
|
s_clock.start();
|
2011-10-19 23:04:52 +00:00
|
|
|
/* this is the same as the QTimer::singleShot(0, SLOT(init())) kludge
|
|
|
|
* defering the init and esp. the connection to the windowClosed slot */
|
|
|
|
QMetaObject::invokeMethod( this, "init", Qt::QueuedConnection );
|
|
|
|
}
|
|
|
|
|
2016-02-01 20:43:18 +00:00
|
|
|
AnimationEffect::~AnimationEffect()
|
|
|
|
{
|
|
|
|
delete d_ptr;
|
|
|
|
}
|
|
|
|
|
2011-10-19 23:04:52 +00:00
|
|
|
void AnimationEffect::init()
|
|
|
|
{
|
2013-05-09 15:39:21 +00:00
|
|
|
Q_D(AnimationEffect);
|
|
|
|
if (d->m_isInitialized)
|
|
|
|
return; // not more than once, please
|
|
|
|
d->m_isInitialized = true;
|
2011-10-19 23:04:52 +00:00
|
|
|
/* by connecting the signal from a slot AFTER the inheriting class constructor had the chance to
|
|
|
|
* connect it we can provide auto-referencing of animated and closed windows, since at the time
|
|
|
|
* our slot will be called, the slot of the subclass has been (SIGNAL/SLOT connections are FIFO)
|
|
|
|
* and has pot. started an animation so we have the window in our hash :) */
|
2019-01-19 18:27:17 +00:00
|
|
|
connect(effects, &EffectsHandler::windowClosed, this, &AnimationEffect::_windowClosed);
|
|
|
|
connect(effects, &EffectsHandler::windowDeleted, this, &AnimationEffect::_windowDeleted);
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool AnimationEffect::isActive() const
|
|
|
|
{
|
|
|
|
Q_D(const AnimationEffect);
|
|
|
|
return !d->m_animations.isEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-10-22 21:10:02 +00:00
|
|
|
#define RELATIVE_XY(_FIELD_) const bool relative[2] = { static_cast<bool>(metaData(Relative##_FIELD_##X, meta)), \
|
|
|
|
static_cast<bool>(metaData(Relative##_FIELD_##Y, meta)) }
|
2011-10-19 23:04:52 +00:00
|
|
|
|
2016-02-03 16:36:48 +00:00
|
|
|
void AnimationEffect::validate(Attribute a, uint &meta, FPx2 *from, FPx2 *to, const EffectWindow *w) const
|
2011-10-19 23:04:52 +00:00
|
|
|
{
|
|
|
|
if (a < NonFloatBase) {
|
|
|
|
if (a == Scale) {
|
|
|
|
QRect area = effects->clientArea(ScreenArea , w);
|
2016-02-03 16:36:48 +00:00
|
|
|
if (from && from->isValid()) {
|
2011-10-19 23:04:52 +00:00
|
|
|
RELATIVE_XY(Source);
|
2016-02-03 16:36:48 +00:00
|
|
|
from->set(relative[0] ? (*from)[0] * area.width() / w->width() : (*from)[0],
|
|
|
|
relative[1] ? (*from)[1] * area.height() / w->height() : (*from)[1]);
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
2016-02-03 16:36:48 +00:00
|
|
|
if (to && to->isValid()) {
|
2011-10-19 23:04:52 +00:00
|
|
|
RELATIVE_XY(Target);
|
2016-02-03 16:36:48 +00:00
|
|
|
to->set(relative[0] ? (*to)[0] * area.width() / w->width() : (*to)[0],
|
|
|
|
relative[1] ? (*to)[1] * area.height() / w->height() : (*to)[1] );
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
} else if (a == Rotation) {
|
2016-02-03 16:36:48 +00:00
|
|
|
if (from && !from->isValid()) {
|
|
|
|
setMetaData(SourceAnchor, metaData(TargetAnchor, meta), meta);
|
|
|
|
from->set(0.0,0.0);
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
2016-02-03 16:36:48 +00:00
|
|
|
if (to && !to->isValid()) {
|
|
|
|
setMetaData(TargetAnchor, metaData(SourceAnchor, meta), meta);
|
|
|
|
to->set(0.0,0.0);
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-03 16:36:48 +00:00
|
|
|
if (from && !from->isValid())
|
|
|
|
from->set(1.0,1.0);
|
|
|
|
if (to && !to->isValid())
|
|
|
|
to->set(1.0,1.0);
|
2011-10-19 23:04:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
} else if (a == Position) {
|
|
|
|
QRect area = effects->clientArea(ScreenArea , w);
|
2011-11-29 21:56:56 +00:00
|
|
|
QPoint pt = w->geometry().bottomRight(); // cannot be < 0 ;-)
|
2016-02-03 16:36:48 +00:00
|
|
|
if (from) {
|
|
|
|
if (from->isValid()) {
|
|
|
|
RELATIVE_XY(Source);
|
|
|
|
from->set(relative[0] ? area.x() + (*from)[0] * area.width() : (*from)[0],
|
|
|
|
relative[1] ? area.y() + (*from)[1] * area.height() : (*from)[1]);
|
|
|
|
} else {
|
|
|
|
from->set(pt.x(), pt.y());
|
|
|
|
setMetaData(SourceAnchor, AnimationEffect::Bottom|AnimationEffect::Right, meta);
|
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
|
2016-02-03 16:36:48 +00:00
|
|
|
if (to) {
|
|
|
|
if (to->isValid()) {
|
|
|
|
RELATIVE_XY(Target);
|
|
|
|
to->set(relative[0] ? area.x() + (*to)[0] * area.width() : (*to)[0],
|
|
|
|
relative[1] ? area.y() + (*to)[1] * area.height() : (*to)[1]);
|
|
|
|
} else {
|
|
|
|
to->set(pt.x(), pt.y());
|
|
|
|
setMetaData( TargetAnchor, AnimationEffect::Bottom|AnimationEffect::Right, meta );
|
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else if (a == Size) {
|
|
|
|
QRect area = effects->clientArea(ScreenArea , w);
|
2016-02-03 16:36:48 +00:00
|
|
|
if (from) {
|
|
|
|
if (from->isValid()) {
|
|
|
|
RELATIVE_XY(Source);
|
|
|
|
from->set(relative[0] ? (*from)[0] * area.width() : (*from)[0],
|
|
|
|
relative[1] ? (*from)[1] * area.height() : (*from)[1]);
|
|
|
|
} else {
|
|
|
|
from->set(w->width(), w->height());
|
|
|
|
}
|
2012-03-24 21:42:29 +00:00
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
|
2016-02-03 16:36:48 +00:00
|
|
|
if (to) {
|
2016-02-16 14:07:09 +00:00
|
|
|
if (to->isValid()) {
|
2016-02-03 16:36:48 +00:00
|
|
|
RELATIVE_XY(Target);
|
|
|
|
to->set(relative[0] ? (*to)[0] * area.width() : (*to)[0],
|
|
|
|
relative[1] ? (*to)[1] * area.height() : (*to)[1]);
|
|
|
|
} else {
|
|
|
|
to->set(w->width(), w->height());
|
|
|
|
}
|
2012-03-24 21:42:29 +00:00
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
|
|
|
|
} else if (a == Translation) {
|
|
|
|
QRect area = w->rect();
|
2016-02-03 16:36:48 +00:00
|
|
|
if (from) {
|
|
|
|
if (from->isValid()) {
|
|
|
|
RELATIVE_XY(Source);
|
|
|
|
from->set(relative[0] ? (*from)[0] * area.width() : (*from)[0],
|
|
|
|
relative[1] ? (*from)[1] * area.height() : (*from)[1]);
|
|
|
|
} else {
|
|
|
|
from->set(0.0, 0.0);
|
|
|
|
}
|
2012-03-24 21:42:29 +00:00
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
|
2016-02-03 16:36:48 +00:00
|
|
|
if (to) {
|
2016-02-16 14:07:09 +00:00
|
|
|
if (to->isValid()) {
|
2016-02-03 16:36:48 +00:00
|
|
|
RELATIVE_XY(Target);
|
|
|
|
to->set(relative[0] ? (*to)[0] * area.width() : (*to)[0],
|
|
|
|
relative[1] ? (*to)[1] * area.height() : (*to)[1]);
|
|
|
|
} else {
|
|
|
|
to->set(0.0, 0.0);
|
|
|
|
}
|
2012-03-24 21:42:29 +00:00
|
|
|
}
|
2016-02-03 16:36:48 +00:00
|
|
|
|
2012-03-28 21:13:37 +00:00
|
|
|
} else if (a == Clip) {
|
2016-02-03 16:36:48 +00:00
|
|
|
if (from && !from->isValid()) {
|
|
|
|
from->set(1.0,1.0);
|
|
|
|
setMetaData(SourceAnchor, metaData(TargetAnchor, meta), meta);
|
2012-03-28 21:13:37 +00:00
|
|
|
}
|
2016-02-03 16:36:48 +00:00
|
|
|
if (to && !to->isValid()) {
|
|
|
|
to->set(1.0,1.0);
|
|
|
|
setMetaData(TargetAnchor, metaData(SourceAnchor, meta), meta);
|
2012-03-28 21:13:37 +00:00
|
|
|
}
|
2016-02-03 16:36:48 +00:00
|
|
|
|
2013-05-13 06:17:28 +00:00
|
|
|
} else if (a == CrossFadePrevious) {
|
2016-02-03 16:36:48 +00:00
|
|
|
if (from && !from->isValid()) {
|
|
|
|
from->set(0.0);
|
2013-05-13 06:17:28 +00:00
|
|
|
}
|
2016-02-03 16:36:48 +00:00
|
|
|
if (to && !to->isValid()) {
|
|
|
|
to->set(1.0);
|
2013-05-13 06:17:28 +00:00
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
2016-02-03 16:36:48 +00:00
|
|
|
}
|
|
|
|
|
[effects/dialogparent] Fix flickering of parent windows
Summary:
If a modal window is closed and some alternative effect that animates
the disappearing of windows is enabled(e.g. the Glide effect, or the
Scale effect), the Dialog Parent effect can cause flickering of the
parent window because its animation duration doesn't match duration of
those alternative effects.
Also, if the Fade effect, the Glide effect, and the Scale effect are
disabled, the Dialog Parent will keep the parent window alive for no
good reason.
This change addresses that problem by adding keepAlive property to
`animate` function so scripted effects have more control over lifetime
of animated windows.
If both a modal window and its parent window are closed at the same time
(and there is no effect that animates the disappearing of windows), the
Dialog Parent will stop immediately(because windowDeleted will be
emitted right after windowClosed signal).
If both a modal window and its parent window are closed at the same time
(and there is effect that animates the disappearing of windows), the
Dialog Parent won't reference the latter window. Thus, it won't cause
flickering. I.e. it will "passively" animate parent windows.
BUG: 355036
FIXED-IN: 5.15.0
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D14919
2018-10-10 07:36:45 +00:00
|
|
|
quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect, bool keepAlive)
|
2016-02-03 16:36:48 +00:00
|
|
|
{
|
|
|
|
const bool waitAtSource = from.isValid();
|
|
|
|
validate(a, meta, &from, &to, w);
|
2011-10-19 23:04:52 +00:00
|
|
|
|
|
|
|
Q_D(AnimationEffect);
|
2013-05-09 15:39:21 +00:00
|
|
|
if (!d->m_isInitialized)
|
|
|
|
init(); // needs to ensure the window gets removed if deleted in the same event cycle
|
2013-03-18 16:08:58 +00:00
|
|
|
if (d->m_animations.isEmpty()) {
|
2019-01-19 18:27:17 +00:00
|
|
|
connect(effects, &EffectsHandler::windowGeometryShapeChanged,
|
|
|
|
this, &AnimationEffect::_expandedGeometryChanged);
|
|
|
|
connect(effects, &EffectsHandler::windowStepUserMovedResized,
|
|
|
|
this, &AnimationEffect::_expandedGeometryChanged);
|
|
|
|
connect(effects, &EffectsHandler::windowPaddingChanged,
|
|
|
|
this, &AnimationEffect::_expandedGeometryChanged);
|
2013-03-18 16:08:58 +00:00
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
AniMap::iterator it = d->m_animations.find(w);
|
|
|
|
if (it == d->m_animations.end())
|
2012-03-24 21:42:29 +00:00
|
|
|
it = d->m_animations.insert(w, QPair<QList<AniData>, QRect>(QList<AniData>(), QRect()));
|
2018-10-03 00:11:59 +00:00
|
|
|
|
|
|
|
FullScreenEffectLockPtr fullscreen;
|
|
|
|
if (fullScreenEffect) {
|
|
|
|
if (d->m_fullScreenEffectLock.isNull()) {
|
|
|
|
fullscreen = FullScreenEffectLockPtr::create(this);
|
|
|
|
d->m_fullScreenEffectLock = fullscreen.toWeakRef();
|
|
|
|
} else {
|
|
|
|
fullscreen = d->m_fullScreenEffectLock.toStrongRef();
|
|
|
|
}
|
|
|
|
}
|
2018-10-23 17:21:27 +00:00
|
|
|
|
|
|
|
PreviousWindowPixmapLockPtr previousPixmap;
|
|
|
|
if (a == CrossFadePrevious) {
|
|
|
|
previousPixmap = PreviousWindowPixmapLockPtr::create(w);
|
|
|
|
}
|
|
|
|
|
|
|
|
it->first.append(AniData(
|
|
|
|
a, // Attribute
|
|
|
|
meta, // Metadata
|
|
|
|
to, // Target
|
|
|
|
delay, // Delay
|
|
|
|
from, // Source
|
|
|
|
waitAtSource, // Whether the animation should be kept at source
|
|
|
|
fullscreen, // Full screen effect lock
|
|
|
|
keepAlive, // Keep alive flag
|
|
|
|
previousPixmap // Previous window pixmap lock
|
|
|
|
));
|
|
|
|
|
2018-10-24 16:39:37 +00:00
|
|
|
const quint64 ret_id = ++d->m_animCounter;
|
|
|
|
AniData &animation = it->first.last();
|
|
|
|
animation.id = ret_id;
|
|
|
|
|
|
|
|
animation.timeLine.setDirection(TimeLine::Forward);
|
|
|
|
animation.timeLine.setDuration(std::chrono::milliseconds(ms));
|
|
|
|
animation.timeLine.setEasingCurve(curve);
|
|
|
|
animation.timeLine.setSourceRedirectMode(TimeLine::RedirectMode::Strict);
|
|
|
|
animation.timeLine.setTargetRedirectMode(TimeLine::RedirectMode::Relaxed);
|
|
|
|
|
[scripting] Introduce redirect function
Summary:
Consider current implementation of the Squash effect: if a window was
minimized, an animation will be started; if the window is unminimized
and the animation is still active (that can happen when user clicks on
app's icon really fast), the animation will be stopped and a new one will
be created. Such behavior can lead to rapid jumps in the observed
"animation".
A better approach would be first try to **reverse** the already active
animation, and if that attempt wasn't successful, start a new animation.
This patch introduces a new function to the scripted effects API that
lets JavaScript effects to control direction of animations. The
prototype of the function looks as follows:
redirect(<animation id(s)>, <direction>, [<termination policy>])
the first argument is an animation id or a list of animation ids, the
second argument specifies the new direction of the animation or
animations if a list of ids was passed as the first argument. The
third argument specifies whether the animation(s) should be terminated
when it(they) reaches the source position, currently it's relevant only
for animations that are created with set() function. The termination
policy argument is optional, by default it's Effect.TerminateAtSource.
We can use this function to fix issues with rapid jumps in the Squash
effect. Also, redirect() lets us to write effects for simple animations
in slightly different style: first, we have to start the main animation
(e.g. for the Dialog Parent effect, it would be dimming of main windows)
and then change direction of the animation depending on external events,
e.g. when the Desktop Cube effect is activated.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, abetts, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D16449
2018-10-24 19:58:29 +00:00
|
|
|
animation.terminationFlags = TerminateAtSource;
|
|
|
|
if (!keepAtTarget) {
|
|
|
|
animation.terminationFlags |= TerminateAtTarget;
|
|
|
|
}
|
|
|
|
|
2012-03-24 21:42:29 +00:00
|
|
|
it->second = QRect();
|
2011-10-19 23:04:52 +00:00
|
|
|
|
2013-01-03 23:00:39 +00:00
|
|
|
d->m_animationsTouched = true;
|
|
|
|
|
2012-03-24 21:42:29 +00:00
|
|
|
if (delay > 0) {
|
2019-01-19 18:27:17 +00:00
|
|
|
QTimer::singleShot(delay, this, &AnimationEffect::triggerRepaint);
|
2014-11-25 07:40:23 +00:00
|
|
|
const QSize &s = effects->virtualScreenSize();
|
2012-03-24 21:42:29 +00:00
|
|
|
if (waitAtSource)
|
2014-11-25 07:40:23 +00:00
|
|
|
w->addLayerRepaint(0, 0, s.width(), s.height());
|
2012-03-24 21:42:29 +00:00
|
|
|
}
|
|
|
|
else {
|
2011-10-19 23:04:52 +00:00
|
|
|
triggerRepaint();
|
2012-03-24 21:42:29 +00:00
|
|
|
}
|
2013-02-28 18:51:00 +00:00
|
|
|
return ret_id;
|
|
|
|
}
|
|
|
|
|
2016-02-03 16:36:48 +00:00
|
|
|
bool AnimationEffect::retarget(quint64 animationId, FPx2 newTarget, int newRemainingTime)
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
|
|
|
if (animationId == d->m_justEndedAnimation)
|
|
|
|
return false; // this is just ending, do not try to retarget it
|
|
|
|
for (AniMap::iterator entry = d->m_animations.begin(),
|
|
|
|
mapEnd = d->m_animations.end(); entry != mapEnd; ++entry) {
|
|
|
|
for (QList<AniData>::iterator anim = entry->first.begin(),
|
|
|
|
animEnd = entry->first.end(); anim != animEnd; ++anim) {
|
2016-03-04 17:17:42 +00:00
|
|
|
if (anim->id == animationId) {
|
2016-02-03 16:36:48 +00:00
|
|
|
anim->from.set(interpolated(*anim, 0), interpolated(*anim, 1));
|
|
|
|
validate(anim->attribute, anim->meta, nullptr, &newTarget, entry.key());
|
|
|
|
anim->to.set(newTarget[0], newTarget[1]);
|
2018-10-24 16:39:37 +00:00
|
|
|
|
|
|
|
anim->timeLine.setDirection(TimeLine::Forward);
|
|
|
|
anim->timeLine.setDuration(std::chrono::milliseconds(newRemainingTime));
|
|
|
|
anim->timeLine.reset();
|
|
|
|
|
2016-02-03 16:36:48 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false; // no animation found
|
|
|
|
}
|
|
|
|
|
[scripting] Introduce redirect function
Summary:
Consider current implementation of the Squash effect: if a window was
minimized, an animation will be started; if the window is unminimized
and the animation is still active (that can happen when user clicks on
app's icon really fast), the animation will be stopped and a new one will
be created. Such behavior can lead to rapid jumps in the observed
"animation".
A better approach would be first try to **reverse** the already active
animation, and if that attempt wasn't successful, start a new animation.
This patch introduces a new function to the scripted effects API that
lets JavaScript effects to control direction of animations. The
prototype of the function looks as follows:
redirect(<animation id(s)>, <direction>, [<termination policy>])
the first argument is an animation id or a list of animation ids, the
second argument specifies the new direction of the animation or
animations if a list of ids was passed as the first argument. The
third argument specifies whether the animation(s) should be terminated
when it(they) reaches the source position, currently it's relevant only
for animations that are created with set() function. The termination
policy argument is optional, by default it's Effect.TerminateAtSource.
We can use this function to fix issues with rapid jumps in the Squash
effect. Also, redirect() lets us to write effects for simple animations
in slightly different style: first, we have to start the main animation
(e.g. for the Dialog Parent effect, it would be dimming of main windows)
and then change direction of the animation depending on external events,
e.g. when the Desktop Cube effect is activated.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, abetts, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D16449
2018-10-24 19:58:29 +00:00
|
|
|
bool AnimationEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags)
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
|
|
|
|
|
|
|
if (animationId == d->m_justEndedAnimation) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto entryIt = d->m_animations.begin(); entryIt != d->m_animations.end(); ++entryIt) {
|
|
|
|
auto animIt = std::find_if(entryIt->first.begin(), entryIt->first.end(),
|
|
|
|
[animationId] (AniData &anim) {
|
|
|
|
return anim.id == animationId;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
if (animIt == entryIt->first.end()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (direction) {
|
|
|
|
case Backward:
|
|
|
|
animIt->timeLine.setDirection(TimeLine::Backward);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Forward:
|
|
|
|
animIt->timeLine.setDirection(TimeLine::Forward);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
animIt->terminationFlags = terminationFlags & ~TerminateAtTarget;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-10-25 18:02:36 +00:00
|
|
|
bool AnimationEffect::complete(quint64 animationId)
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
|
|
|
|
|
|
|
if (animationId == d->m_justEndedAnimation) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto entryIt = d->m_animations.begin(); entryIt != d->m_animations.end(); ++entryIt) {
|
|
|
|
auto animIt = std::find_if(entryIt->first.begin(), entryIt->first.end(),
|
|
|
|
[animationId] (AniData &anim) {
|
|
|
|
return anim.id == animationId;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
if (animIt == entryIt->first.end()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
animIt->timeLine.setElapsed(animIt->timeLine.duration());
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-02-28 18:51:00 +00:00
|
|
|
bool AnimationEffect::cancel(quint64 animationId)
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
2016-02-03 11:53:37 +00:00
|
|
|
if (animationId == d->m_justEndedAnimation)
|
|
|
|
return true; // this is just ending, do not try to cancel it but fake success
|
2013-02-28 18:51:00 +00:00
|
|
|
for (AniMap::iterator entry = d->m_animations.begin(), mapEnd = d->m_animations.end(); entry != mapEnd; ++entry) {
|
|
|
|
for (QList<AniData>::iterator anim = entry->first.begin(), animEnd = entry->first.end(); anim != animEnd; ++anim) {
|
2016-03-04 17:17:42 +00:00
|
|
|
if (anim->id == animationId) {
|
2013-02-28 18:51:00 +00:00
|
|
|
entry->first.erase(anim); // remove the animation
|
|
|
|
if (entry->first.isEmpty()) { // no other animations on the window, release it.
|
|
|
|
d->m_animations.erase(entry);
|
|
|
|
}
|
2013-03-18 16:08:58 +00:00
|
|
|
if (d->m_animations.isEmpty())
|
|
|
|
disconnectGeometryChanges();
|
2016-02-03 11:53:37 +00:00
|
|
|
d->m_animationsTouched = true; // could be called from animationEnded
|
2013-02-28 18:51:00 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AnimationEffect::prePaintScreen( ScreenPrePaintData& data, int time )
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
|
|
|
if (d->m_animations.isEmpty()) {
|
|
|
|
effects->prePaintScreen(data, time);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-01-03 23:00:39 +00:00
|
|
|
d->m_animationsTouched = false;
|
2012-03-24 21:42:29 +00:00
|
|
|
AniMap::iterator entry = d->m_animations.begin(), mapEnd = d->m_animations.end();
|
2011-10-19 23:04:52 +00:00
|
|
|
d->m_animated = false;
|
2012-03-24 21:42:29 +00:00
|
|
|
// short int transformed = 0;
|
|
|
|
while (entry != mapEnd) {
|
|
|
|
bool invalidateLayerRect = false;
|
|
|
|
QList<AniData>::iterator anim = entry->first.begin(), animEnd = entry->first.end();
|
2013-01-03 23:00:39 +00:00
|
|
|
int animCounter = 0;
|
2012-03-24 21:42:29 +00:00
|
|
|
while (anim != animEnd) {
|
2012-11-03 20:33:34 +00:00
|
|
|
if (anim->startTime > clock()) {
|
2011-10-19 23:04:52 +00:00
|
|
|
if (!anim->waitAtSource) {
|
|
|
|
++anim;
|
2013-01-03 23:00:39 +00:00
|
|
|
++animCounter;
|
2011-10-19 23:04:52 +00:00
|
|
|
continue;
|
|
|
|
}
|
2012-03-24 21:42:29 +00:00
|
|
|
} else {
|
2018-10-24 16:39:37 +00:00
|
|
|
anim->timeLine.update(std::chrono::milliseconds(time));
|
2012-03-24 21:42:29 +00:00
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
|
[scripting] Introduce redirect function
Summary:
Consider current implementation of the Squash effect: if a window was
minimized, an animation will be started; if the window is unminimized
and the animation is still active (that can happen when user clicks on
app's icon really fast), the animation will be stopped and a new one will
be created. Such behavior can lead to rapid jumps in the observed
"animation".
A better approach would be first try to **reverse** the already active
animation, and if that attempt wasn't successful, start a new animation.
This patch introduces a new function to the scripted effects API that
lets JavaScript effects to control direction of animations. The
prototype of the function looks as follows:
redirect(<animation id(s)>, <direction>, [<termination policy>])
the first argument is an animation id or a list of animation ids, the
second argument specifies the new direction of the animation or
animations if a list of ids was passed as the first argument. The
third argument specifies whether the animation(s) should be terminated
when it(they) reaches the source position, currently it's relevant only
for animations that are created with set() function. The termination
policy argument is optional, by default it's Effect.TerminateAtSource.
We can use this function to fix issues with rapid jumps in the Squash
effect. Also, redirect() lets us to write effects for simple animations
in slightly different style: first, we have to start the main animation
(e.g. for the Dialog Parent effect, it would be dimming of main windows)
and then change direction of the animation depending on external events,
e.g. when the Desktop Cube effect is activated.
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, abetts, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D16449
2018-10-24 19:58:29 +00:00
|
|
|
if (anim->isActive()) {
|
2012-03-24 21:42:29 +00:00
|
|
|
// if (anim->attribute != Brightness && anim->attribute != Saturation && anim->attribute != Opacity)
|
|
|
|
// transformed = true;
|
2011-10-19 23:04:52 +00:00
|
|
|
d->m_animated = true;
|
|
|
|
++anim;
|
2013-01-03 23:00:39 +00:00
|
|
|
++animCounter;
|
2012-03-24 21:42:29 +00:00
|
|
|
} else {
|
2013-01-03 23:00:39 +00:00
|
|
|
EffectWindow *oldW = entry.key();
|
2016-03-04 17:17:42 +00:00
|
|
|
d->m_justEndedAnimation = anim->id;
|
2013-01-03 23:00:39 +00:00
|
|
|
animationEnded(oldW, anim->attribute, anim->meta);
|
2016-02-03 11:53:37 +00:00
|
|
|
d->m_justEndedAnimation = 0;
|
2013-01-03 23:00:39 +00:00
|
|
|
// NOTICE animationEnded is an external call and might have called "::animate"
|
|
|
|
// as a result our iterators could now point random junk on the heap
|
|
|
|
// so we've to restore the former states, ie. find our window list and animation
|
|
|
|
if (d->m_animationsTouched) {
|
|
|
|
d->m_animationsTouched = false;
|
|
|
|
entry = d->m_animations.begin(), mapEnd = d->m_animations.end();
|
|
|
|
while (entry.key() != oldW && entry != mapEnd)
|
|
|
|
++entry;
|
|
|
|
Q_ASSERT(entry != mapEnd); // usercode should not delete animations from animationEnded (not even possible atm.)
|
|
|
|
anim = entry->first.begin(), animEnd = entry->first.end();
|
|
|
|
Q_ASSERT(animCounter < entry->first.count());
|
|
|
|
for (int i = 0; i < animCounter; ++i)
|
|
|
|
++anim;
|
|
|
|
}
|
2012-03-24 21:42:29 +00:00
|
|
|
anim = entry->first.erase(anim);
|
|
|
|
invalidateLayerRect = d->m_damageDirty = true;
|
|
|
|
animEnd = entry->first.end();
|
2011-12-09 13:39:54 +00:00
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
2012-03-24 21:42:29 +00:00
|
|
|
if (entry->first.isEmpty()) {
|
|
|
|
data.paint |= entry->second;
|
|
|
|
// d->m_damageDirty = true; // TODO likely no longer required
|
2011-10-19 23:04:52 +00:00
|
|
|
entry = d->m_animations.erase(entry);
|
2012-03-24 21:42:29 +00:00
|
|
|
mapEnd = d->m_animations.end();
|
|
|
|
} else {
|
|
|
|
if (invalidateLayerRect)
|
|
|
|
*const_cast<QRect*>(&(entry->second)) = QRect(); // invalidate
|
2011-10-19 23:04:52 +00:00
|
|
|
++entry;
|
2012-03-24 21:42:29 +00:00
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// janitorial...
|
2013-03-18 16:08:58 +00:00
|
|
|
if (d->m_animations.isEmpty()) {
|
|
|
|
disconnectGeometryChanges();
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
effects->prePaintScreen(data, time);
|
|
|
|
}
|
|
|
|
|
2012-03-28 21:13:37 +00:00
|
|
|
static int xCoord(const QRect &r, int flag) {
|
|
|
|
if (flag & AnimationEffect::Left)
|
|
|
|
return r.x();
|
|
|
|
else if (flag & AnimationEffect::Right)
|
|
|
|
return r.right();
|
|
|
|
else
|
|
|
|
return r.x() + r.width()/2;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int yCoord(const QRect &r, int flag) {
|
|
|
|
if (flag & AnimationEffect::Top)
|
|
|
|
return r.y();
|
|
|
|
else if (flag & AnimationEffect::Bottom)
|
|
|
|
return r.bottom();
|
|
|
|
else
|
|
|
|
return r.y() + r.height()/2;
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect AnimationEffect::clipRect(const QRect &geo, const AniData &anim) const
|
|
|
|
{
|
|
|
|
QRect clip = geo;
|
|
|
|
FPx2 ratio = anim.from + progress(anim) * (anim.to - anim.from);
|
|
|
|
if (anim.from[0] < 1.0 || anim.to[0] < 1.0) {
|
|
|
|
clip.setWidth(clip.width() * ratio[0]);
|
|
|
|
}
|
|
|
|
if (anim.from[1] < 1.0 || anim.to[1] < 1.0) {
|
|
|
|
clip.setHeight(clip.height() * ratio[1]);
|
|
|
|
}
|
|
|
|
const QRect center = geo.adjusted(clip.width()/2, clip.height()/2,
|
|
|
|
-(clip.width()+1)/2, -(clip.height()+1)/2 );
|
|
|
|
const int x[2] = { xCoord(center, metaData(SourceAnchor, anim.meta)),
|
|
|
|
xCoord(center, metaData(TargetAnchor, anim.meta)) };
|
|
|
|
const int y[2] = { yCoord(center, metaData(SourceAnchor, anim.meta)),
|
|
|
|
yCoord(center, metaData(TargetAnchor, anim.meta)) };
|
|
|
|
const QPoint d(x[0] + ratio[0]*(x[1]-x[0]), y[0] + ratio[1]*(y[1]-y[0]));
|
|
|
|
clip.moveTopLeft(QPoint(d.x() - clip.width()/2, d.y() - clip.height()/2));
|
|
|
|
return clip;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AnimationEffect::clipWindow(const EffectWindow *w, const AniData &anim, WindowQuadList &quads) const
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
const QRect geo = w->expandedGeometry();
|
|
|
|
QRect clip = AnimationEffect::clipRect(geo, anim);
|
|
|
|
WindowQuadList filtered;
|
|
|
|
if (clip.left() != geo.left()) {
|
|
|
|
quads = quads.splitAtX(clip.left());
|
|
|
|
foreach (const WindowQuad &quad, quads) {
|
|
|
|
if (quad.right() >= clip.left())
|
|
|
|
filtered << quad;
|
|
|
|
}
|
|
|
|
quads = filtered;
|
|
|
|
filtered.clear();
|
|
|
|
}
|
|
|
|
if (clip.right() != geo.right()) {
|
|
|
|
quads = quads.splitAtX(clip.left());
|
|
|
|
foreach (const WindowQuad &quad, quads) {
|
|
|
|
if (quad.right() <= clip.right())
|
|
|
|
filtered << quad;
|
|
|
|
}
|
|
|
|
quads = filtered;
|
|
|
|
filtered.clear();
|
|
|
|
}
|
|
|
|
if (clip.top() != geo.top()) {
|
|
|
|
quads = quads.splitAtY(clip.top());
|
|
|
|
foreach (const WindowQuad &quad, quads) {
|
|
|
|
if (quad.top() >= clip.top())
|
|
|
|
filtered << quad;
|
|
|
|
}
|
|
|
|
quads = filtered;
|
|
|
|
filtered.clear();
|
|
|
|
}
|
|
|
|
if (clip.bottom() != geo.bottom()) {
|
|
|
|
quads = quads.splitAtY(clip.bottom());
|
|
|
|
foreach (const WindowQuad &quad, quads) {
|
|
|
|
if (quad.bottom() <= clip.bottom())
|
|
|
|
filtered << quad;
|
|
|
|
}
|
|
|
|
quads = filtered;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-18 16:08:58 +00:00
|
|
|
void AnimationEffect::disconnectGeometryChanges()
|
|
|
|
{
|
2019-01-19 18:27:17 +00:00
|
|
|
disconnect(effects, &EffectsHandler::windowGeometryShapeChanged,
|
|
|
|
this, &AnimationEffect::_expandedGeometryChanged);
|
|
|
|
disconnect(effects, &EffectsHandler::windowStepUserMovedResized,
|
|
|
|
this, &AnimationEffect::_expandedGeometryChanged);
|
|
|
|
disconnect(effects, &EffectsHandler::windowPaddingChanged,
|
|
|
|
this, &AnimationEffect::_expandedGeometryChanged);
|
2013-03-18 16:08:58 +00:00
|
|
|
}
|
|
|
|
|
2012-03-28 21:13:37 +00:00
|
|
|
|
2011-10-19 23:04:52 +00:00
|
|
|
void AnimationEffect::prePaintWindow( EffectWindow* w, WindowPrePaintData& data, int time )
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
|
|
|
if ( d->m_animated ) {
|
2011-10-30 19:29:28 +00:00
|
|
|
AniMap::const_iterator entry = d->m_animations.constFind( w );
|
2011-10-19 23:04:52 +00:00
|
|
|
if ( entry != d->m_animations.constEnd() ) {
|
|
|
|
bool isUsed = false;
|
2018-10-31 12:16:46 +00:00
|
|
|
bool paintDeleted = false;
|
2012-03-24 21:42:29 +00:00
|
|
|
for (QList<AniData>::const_iterator anim = entry->first.constBegin(); anim != entry->first.constEnd(); ++anim) {
|
2012-11-03 20:33:34 +00:00
|
|
|
if (anim->startTime > clock() && !anim->waitAtSource)
|
2011-10-19 23:04:52 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
isUsed = true;
|
2014-08-04 19:12:34 +00:00
|
|
|
if (anim->attribute == Opacity || anim->attribute == CrossFadePrevious)
|
2011-10-19 23:04:52 +00:00
|
|
|
data.setTranslucent();
|
2012-03-24 21:42:29 +00:00
|
|
|
else if (!(anim->attribute == Brightness || anim->attribute == Saturation)) {
|
2011-10-19 23:04:52 +00:00
|
|
|
data.setTransformed();
|
2012-03-28 21:13:37 +00:00
|
|
|
if (anim->attribute == Clip)
|
|
|
|
clipWindow(w, *anim, data.quads);
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
2018-10-31 12:16:46 +00:00
|
|
|
|
|
|
|
paintDeleted |= anim->keepAlive;
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
if ( isUsed ) {
|
|
|
|
if ( w->isMinimized() )
|
|
|
|
w->enablePainting( EffectWindow::PAINT_DISABLED_BY_MINIMIZE );
|
2018-10-31 12:16:46 +00:00
|
|
|
else if ( w->isDeleted() && paintDeleted )
|
2011-10-19 23:04:52 +00:00
|
|
|
w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DELETE );
|
|
|
|
else if ( !w->isOnCurrentDesktop() )
|
|
|
|
w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP );
|
2012-03-24 21:42:29 +00:00
|
|
|
// if( !w->isPaintingEnabled() && !effects->activeFullScreenEffect() )
|
|
|
|
// effects->addLayerRepaint(w->expandedGeometry());
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
effects->prePaintWindow( w, data, time );
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline float geometryCompensation(int flags, float v)
|
|
|
|
{
|
|
|
|
if (flags & (AnimationEffect::Left|AnimationEffect::Top))
|
|
|
|
return 0.0; // no compensation required
|
|
|
|
if (flags & (AnimationEffect::Right|AnimationEffect::Bottom))
|
|
|
|
return 1.0 - v; // full compensation
|
|
|
|
return 0.5 * (1.0 - v); // half compensation
|
|
|
|
}
|
|
|
|
|
|
|
|
void AnimationEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data )
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
|
|
|
if ( d->m_animated ) {
|
2011-10-30 19:29:28 +00:00
|
|
|
AniMap::const_iterator entry = d->m_animations.constFind( w );
|
2011-10-19 23:04:52 +00:00
|
|
|
if ( entry != d->m_animations.constEnd() ) {
|
2012-03-24 21:42:29 +00:00
|
|
|
for ( QList<AniData>::const_iterator anim = entry->first.constBegin(); anim != entry->first.constEnd(); ++anim ) {
|
2011-10-19 23:04:52 +00:00
|
|
|
|
2012-11-03 20:33:34 +00:00
|
|
|
if (anim->startTime > clock() && !anim->waitAtSource)
|
2011-10-19 23:04:52 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (anim->attribute) {
|
|
|
|
case Opacity:
|
2012-07-12 15:20:17 +00:00
|
|
|
data.multiplyOpacity(interpolated(*anim)); break;
|
2011-10-19 23:04:52 +00:00
|
|
|
case Brightness:
|
2012-07-12 15:20:17 +00:00
|
|
|
data.multiplyBrightness(interpolated(*anim)); break;
|
2011-10-19 23:04:52 +00:00
|
|
|
case Saturation:
|
2012-07-12 15:20:17 +00:00
|
|
|
data.multiplySaturation(interpolated(*anim)); break;
|
2011-10-19 23:04:52 +00:00
|
|
|
case Scale: {
|
|
|
|
const QSize sz = w->geometry().size();
|
|
|
|
float f1(1.0), f2(0.0);
|
|
|
|
if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) { // scale x
|
|
|
|
f1 = interpolated(*anim, 0);
|
|
|
|
f2 = geometryCompensation( anim->meta & AnimationEffect::Horizontal, f1 );
|
2012-05-28 12:45:46 +00:00
|
|
|
data.translate(f2 * sz.width());
|
2012-05-28 10:00:31 +00:00
|
|
|
data.setXScale(data.xScale() * f1);
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) { // scale y
|
|
|
|
if (!anim->isOneDimensional()) {
|
|
|
|
f1 = interpolated(*anim, 1);
|
|
|
|
f2 = geometryCompensation( anim->meta & AnimationEffect::Vertical, f1 );
|
2012-03-28 21:13:37 +00:00
|
|
|
}
|
|
|
|
else if ( ((anim->meta & AnimationEffect::Vertical)>>1) != (anim->meta & AnimationEffect::Horizontal) )
|
2011-10-19 23:04:52 +00:00
|
|
|
f2 = geometryCompensation( anim->meta & AnimationEffect::Vertical, f1 );
|
2012-05-28 12:45:46 +00:00
|
|
|
data.translate(0.0, f2 * sz.height());
|
2012-05-28 10:00:31 +00:00
|
|
|
data.setYScale(data.yScale() * f1);
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2012-03-28 21:13:37 +00:00
|
|
|
case Clip:
|
|
|
|
region = clipRect(w->expandedGeometry(), *anim);
|
|
|
|
break;
|
2011-10-19 23:04:52 +00:00
|
|
|
case Translation:
|
2012-05-28 12:45:46 +00:00
|
|
|
data += QPointF(interpolated(*anim, 0), interpolated(*anim, 1));
|
2011-10-19 23:04:52 +00:00
|
|
|
break;
|
|
|
|
case Size: {
|
|
|
|
FPx2 dest = anim->from + progress(*anim) * (anim->to - anim->from);
|
|
|
|
const QSize sz = w->geometry().size();
|
|
|
|
float f;
|
|
|
|
if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) { // resize x
|
|
|
|
f = dest[0]/sz.width();
|
2012-05-28 12:45:46 +00:00
|
|
|
data.translate(geometryCompensation( anim->meta & AnimationEffect::Horizontal, f ) * sz.width());
|
2012-05-28 10:00:31 +00:00
|
|
|
data.setXScale(data.xScale() * f);
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) { // resize y
|
|
|
|
f = dest[1]/sz.height();
|
2012-05-28 12:45:46 +00:00
|
|
|
data.translate(0.0, geometryCompensation( anim->meta & AnimationEffect::Vertical, f ) * sz.height());
|
2012-05-28 10:00:31 +00:00
|
|
|
data.setYScale(data.yScale() * f);
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Position: {
|
|
|
|
const QRect geo = w->geometry();
|
|
|
|
const float prgrs = progress(*anim);
|
|
|
|
if ( anim->from[0] >= 0.0 && anim->to[0] >= 0.0 ) {
|
|
|
|
float dest = interpolated(*anim, 0);
|
|
|
|
const int x[2] = { xCoord(geo, metaData(SourceAnchor, anim->meta)),
|
|
|
|
xCoord(geo, metaData(TargetAnchor, anim->meta)) };
|
2012-05-28 12:45:46 +00:00
|
|
|
data.translate(dest - (x[0] + prgrs*(x[1] - x[0])));
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
if ( anim->from[1] >= 0.0 && anim->to[1] >= 0.0 ) {
|
|
|
|
float dest = interpolated(*anim, 1);
|
|
|
|
const int y[2] = { yCoord(geo, metaData(SourceAnchor, anim->meta)),
|
|
|
|
yCoord(geo, metaData(TargetAnchor, anim->meta)) };
|
2012-05-28 12:45:46 +00:00
|
|
|
data.translate(0.0, dest - (y[0] + prgrs*(y[1] - y[0])));
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Rotation: {
|
2012-06-02 19:54:18 +00:00
|
|
|
data.setRotationAxis((Qt::Axis)metaData(Axis, anim->meta));
|
2011-10-19 23:04:52 +00:00
|
|
|
const float prgrs = progress(*anim);
|
2012-06-02 19:54:18 +00:00
|
|
|
data.setRotationAngle(anim->from[0] + prgrs*(anim->to[0] - anim->from[0]));
|
2011-10-19 23:04:52 +00:00
|
|
|
|
|
|
|
const QRect geo = w->rect();
|
|
|
|
const uint sAnchor = metaData(SourceAnchor, anim->meta),
|
|
|
|
tAnchor = metaData(TargetAnchor, anim->meta);
|
|
|
|
QPointF pt(xCoord(geo, sAnchor), yCoord(geo, sAnchor));
|
|
|
|
|
|
|
|
if (tAnchor != sAnchor) {
|
|
|
|
QPointF pt2(xCoord(geo, tAnchor), yCoord(geo, tAnchor));
|
2012-01-16 21:05:19 +00:00
|
|
|
pt += static_cast<qreal>(prgrs)*(pt2 - pt);
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
2012-06-02 19:54:18 +00:00
|
|
|
data.setRotationOrigin(QVector3D(pt));
|
2011-10-19 23:04:52 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Generic:
|
|
|
|
genericAnimation(w, data, progress(*anim), anim->meta);
|
|
|
|
break;
|
2013-05-13 06:17:28 +00:00
|
|
|
case CrossFadePrevious:
|
|
|
|
data.setCrossFadeProgress(progress(*anim));
|
|
|
|
break;
|
2011-10-19 23:04:52 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
effects->paintWindow( w, mask, region, data );
|
|
|
|
}
|
|
|
|
|
|
|
|
void AnimationEffect::postPaintScreen()
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
2012-03-24 21:42:29 +00:00
|
|
|
if ( d->m_animated ) {
|
|
|
|
if (d->m_damageDirty)
|
|
|
|
updateLayerRepaints();
|
|
|
|
if (d->m_needSceneRepaint) {
|
|
|
|
effects->addRepaintFull();
|
|
|
|
} else {
|
|
|
|
AniMap::const_iterator it = d->m_animations.constBegin(), end = d->m_animations.constEnd();
|
|
|
|
for (; it != end; ++it) {
|
2013-06-03 06:58:54 +00:00
|
|
|
bool addRepaint = false;
|
|
|
|
QList<AniData>::const_iterator anim = it->first.constBegin();
|
|
|
|
for (; anim != it->first.constEnd(); ++anim) {
|
|
|
|
if (anim->startTime > clock())
|
|
|
|
continue;
|
2018-10-24 16:39:37 +00:00
|
|
|
if (!anim->timeLine.done()) {
|
2013-06-03 06:58:54 +00:00
|
|
|
addRepaint = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (addRepaint) {
|
|
|
|
it.key()->addLayerRepaint(it->second);
|
|
|
|
}
|
2012-03-24 21:42:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
effects->postPaintScreen();
|
|
|
|
}
|
|
|
|
|
|
|
|
float AnimationEffect::interpolated( const AniData &a, int i ) const
|
|
|
|
{
|
2012-11-03 20:33:34 +00:00
|
|
|
if (a.startTime > clock())
|
2011-10-19 23:04:52 +00:00
|
|
|
return a.from[i];
|
2018-10-24 16:39:37 +00:00
|
|
|
if (!a.timeLine.done())
|
|
|
|
return a.from[i] + a.timeLine.value() * (a.to[i] - a.from[i]);
|
2013-02-28 18:51:00 +00:00
|
|
|
return a.to[i]; // we're done and "waiting" at the target value
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
float AnimationEffect::progress( const AniData &a ) const
|
|
|
|
{
|
2018-10-24 16:39:37 +00:00
|
|
|
return a.startTime < clock() ? a.timeLine.value() : 0.0;
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO - get this out of the header - the functionpointer usage of QEasingCurve somehow sucks ;-)
|
|
|
|
// qreal AnimationEffect::qecGaussian(qreal progress) // exp(-5*(2*x-1)^2)
|
|
|
|
// {
|
|
|
|
// progress = 2*progress - 1;
|
|
|
|
// progress *= -5*progress;
|
|
|
|
// return qExp(progress);
|
|
|
|
// }
|
|
|
|
|
|
|
|
int AnimationEffect::metaData( MetaType type, uint meta )
|
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
case SourceAnchor:
|
|
|
|
return ((meta>>5) & 0x1f);
|
|
|
|
case TargetAnchor:
|
|
|
|
return (meta& 0x1f);
|
|
|
|
case RelativeSourceX:
|
|
|
|
case RelativeSourceY:
|
|
|
|
case RelativeTargetX:
|
|
|
|
case RelativeTargetY: {
|
|
|
|
const int shift = 10 + type - RelativeSourceX;
|
|
|
|
return ((meta>>shift) & 1);
|
|
|
|
}
|
|
|
|
case Axis:
|
|
|
|
return ((meta>>10) & 3);
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AnimationEffect::setMetaData( MetaType type, uint value, uint &meta )
|
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
case SourceAnchor:
|
|
|
|
meta &= ~(0x1f<<5);
|
|
|
|
meta |= ((value & 0x1f)<<5);
|
|
|
|
break;
|
|
|
|
case TargetAnchor:
|
|
|
|
meta &= ~(0x1f);
|
|
|
|
meta |= (value & 0x1f);
|
|
|
|
break;
|
|
|
|
case RelativeSourceX:
|
|
|
|
case RelativeSourceY:
|
|
|
|
case RelativeTargetX:
|
|
|
|
case RelativeTargetY: {
|
|
|
|
const int shift = 10 + type - RelativeSourceX;
|
|
|
|
if (value)
|
|
|
|
meta |= (1<<shift);
|
|
|
|
else
|
|
|
|
meta &= ~(1<<shift);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Axis:
|
|
|
|
meta &= ~(3<<10);
|
|
|
|
meta |= ((value & 3)<<10);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AnimationEffect::triggerRepaint()
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
2012-03-24 21:42:29 +00:00
|
|
|
for (AniMap::const_iterator entry = d->m_animations.constBegin(), mapEnd = d->m_animations.constEnd(); entry != mapEnd; ++entry)
|
|
|
|
*const_cast<QRect*>(&(entry->second)) = QRect();
|
|
|
|
updateLayerRepaints();
|
|
|
|
if (d->m_needSceneRepaint) {
|
2012-02-12 16:34:47 +00:00
|
|
|
effects->addRepaintFull();
|
2012-03-24 21:42:29 +00:00
|
|
|
} else {
|
|
|
|
AniMap::const_iterator it = d->m_animations.constBegin(), end = d->m_animations.constEnd();
|
2013-06-03 06:58:54 +00:00
|
|
|
for (; it != end; ++it) {
|
2012-03-24 21:42:29 +00:00
|
|
|
it.key()->addLayerRepaint(it->second);
|
2013-06-03 06:58:54 +00:00
|
|
|
}
|
2012-03-24 21:42:29 +00:00
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
|
2012-03-24 21:42:29 +00:00
|
|
|
static float fixOvershoot(float f, const AniData &d, short int dir, float s = 1.1)
|
|
|
|
{
|
2018-10-24 16:39:37 +00:00
|
|
|
switch(d.timeLine.easingCurve().type()) {
|
2012-03-24 21:42:29 +00:00
|
|
|
case QEasingCurve::InOutElastic:
|
|
|
|
case QEasingCurve::InOutBack:
|
|
|
|
return f * s;
|
|
|
|
case QEasingCurve::InElastic:
|
|
|
|
case QEasingCurve::OutInElastic:
|
|
|
|
case QEasingCurve::OutBack:
|
|
|
|
return (dir&2) ? f * s : f;
|
|
|
|
case QEasingCurve::OutElastic:
|
|
|
|
case QEasingCurve::InBack:
|
|
|
|
return (dir&1) ? f * s : f;
|
|
|
|
default:
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AnimationEffect::updateLayerRepaints()
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
|
|
|
d->m_needSceneRepaint = false;
|
|
|
|
for (AniMap::const_iterator entry = d->m_animations.constBegin(), mapEnd = d->m_animations.constEnd(); entry != mapEnd; ++entry) {
|
|
|
|
if (!entry->second.isNull())
|
|
|
|
continue;
|
|
|
|
float f[2] = {1.0, 1.0};
|
|
|
|
float t[2] = {0.0, 0.0};
|
|
|
|
bool createRegion = false;
|
|
|
|
QList<QRect> rects;
|
|
|
|
QRect *layerRect = const_cast<QRect*>(&(entry->second));
|
|
|
|
for (QList<AniData>::const_iterator anim = entry->first.constBegin(), animEnd = entry->first.constEnd(); anim != animEnd; ++anim) {
|
2012-11-03 20:33:34 +00:00
|
|
|
if (anim->startTime > clock())
|
2012-03-24 21:42:29 +00:00
|
|
|
continue;
|
|
|
|
switch (anim->attribute) {
|
|
|
|
case Opacity:
|
|
|
|
case Brightness:
|
|
|
|
case Saturation:
|
2013-05-13 06:17:28 +00:00
|
|
|
case CrossFadePrevious:
|
2012-03-24 21:42:29 +00:00
|
|
|
createRegion = true;
|
|
|
|
break;
|
|
|
|
case Rotation:
|
|
|
|
createRegion = false;
|
2014-11-25 07:40:23 +00:00
|
|
|
*layerRect = QRect(QPoint(0, 0), effects->virtualScreenSize());
|
2012-03-24 21:42:29 +00:00
|
|
|
goto region_creation; // sic! no need to do anything else
|
|
|
|
case Generic:
|
|
|
|
d->m_needSceneRepaint = true; // we don't know whether this will change visual stacking order
|
|
|
|
return; // sic! no need to do anything else
|
|
|
|
case Translation:
|
|
|
|
case Position: {
|
|
|
|
createRegion = true;
|
|
|
|
QRect r(entry.key()->geometry());
|
|
|
|
int x[2] = {0,0};
|
|
|
|
int y[2] = {0,0};
|
|
|
|
if (anim->attribute == Translation) {
|
|
|
|
x[0] = anim->from[0];
|
|
|
|
x[1] = anim->to[0];
|
|
|
|
y[0] = anim->from[1];
|
|
|
|
y[1] = anim->to[1];
|
|
|
|
} else {
|
|
|
|
if ( anim->from[0] >= 0.0 && anim->to[0] >= 0.0 ) {
|
|
|
|
x[0] = anim->from[0] - xCoord(r, metaData(SourceAnchor, anim->meta));
|
|
|
|
x[1] = anim->to[0] - xCoord(r, metaData(TargetAnchor, anim->meta));
|
|
|
|
}
|
|
|
|
if ( anim->from[1] >= 0.0 && anim->to[1] >= 0.0 ) {
|
|
|
|
y[0] = anim->from[1] - yCoord(r, metaData(SourceAnchor, anim->meta));
|
|
|
|
y[1] = anim->to[1] - yCoord(r, metaData(TargetAnchor, anim->meta));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
r = entry.key()->expandedGeometry();
|
|
|
|
rects << r.translated(x[0], y[0]) << r.translated(x[1], y[1]);
|
|
|
|
break;
|
|
|
|
}
|
2012-03-28 21:13:37 +00:00
|
|
|
case Clip:
|
|
|
|
createRegion = true;
|
|
|
|
break;
|
2012-03-24 21:42:29 +00:00
|
|
|
case Size:
|
|
|
|
case Scale: {
|
|
|
|
createRegion = true;
|
|
|
|
const QSize sz = entry.key()->geometry().size();
|
|
|
|
float fx = qMax(fixOvershoot(anim->from[0], *anim, 1), fixOvershoot(anim->to[0], *anim, 2));
|
|
|
|
// float fx = qMax(interpolated(*anim,0), anim->to[0]);
|
|
|
|
if (fx >= 0.0) {
|
|
|
|
if (anim->attribute == Size)
|
|
|
|
fx /= sz.width();
|
|
|
|
f[0] *= fx;
|
|
|
|
t[0] += geometryCompensation( anim->meta & AnimationEffect::Horizontal, fx ) * sz.width();
|
|
|
|
}
|
|
|
|
// float fy = qMax(interpolated(*anim,1), anim->to[1]);
|
|
|
|
float fy = qMax(fixOvershoot(anim->from[1], *anim, 1), fixOvershoot(anim->to[1], *anim, 2));
|
|
|
|
if (fy >= 0.0) {
|
|
|
|
if (anim->attribute == Size)
|
|
|
|
fy /= sz.height();
|
|
|
|
if (!anim->isOneDimensional()) {
|
|
|
|
f[1] *= fy;
|
|
|
|
t[1] += geometryCompensation( anim->meta & AnimationEffect::Vertical, fy ) * sz.height();
|
|
|
|
} else if ( ((anim->meta & AnimationEffect::Vertical)>>1) != (anim->meta & AnimationEffect::Horizontal) ) {
|
|
|
|
f[1] *= fx;
|
|
|
|
t[1] += geometryCompensation( anim->meta & AnimationEffect::Vertical, fx ) * sz.height();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
region_creation:
|
|
|
|
if (createRegion) {
|
|
|
|
const QRect geo = entry.key()->expandedGeometry();
|
|
|
|
if (rects.isEmpty())
|
|
|
|
rects << geo;
|
|
|
|
QList<QRect>::const_iterator r, rEnd = rects.constEnd();
|
|
|
|
for ( r = rects.constBegin(); r != rEnd; ++r) { // transform
|
|
|
|
const_cast<QRect*>(&(*r))->setSize(QSize(qRound(r->width()*f[0]), qRound(r->height()*f[1])));
|
|
|
|
const_cast<QRect*>(&(*r))->translate(t[0], t[1]); // "const_cast" - don't do that at home, kids ;-)
|
|
|
|
}
|
|
|
|
QRect rect = rects.at(0);
|
|
|
|
if (rects.count() > 1) {
|
|
|
|
for ( r = rects.constBegin() + 1; r != rEnd; ++r) // unite
|
|
|
|
rect |= *r;
|
|
|
|
const int dx = 110*(rect.width() - geo.width())/100 + 1 - rect.width() + geo.width();
|
|
|
|
const int dy = 110*(rect.height() - geo.height())/100 + 1 - rect.height() + geo.height();
|
|
|
|
rect.adjust(-dx,-dy,dx,dy); // fix pot. overshoot
|
|
|
|
}
|
|
|
|
*layerRect = rect;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
d->m_damageDirty = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AnimationEffect::_expandedGeometryChanged(KWin::EffectWindow *w, const QRect &old)
|
|
|
|
{
|
2014-02-03 10:36:21 +00:00
|
|
|
Q_UNUSED(old)
|
2012-03-24 21:42:29 +00:00
|
|
|
Q_D(AnimationEffect);
|
|
|
|
AniMap::const_iterator entry = d->m_animations.constFind(w);
|
|
|
|
if (entry != d->m_animations.constEnd()) {
|
|
|
|
*const_cast<QRect*>(&(entry->second)) = QRect();
|
|
|
|
updateLayerRepaints();
|
|
|
|
if (!entry->second.isNull()) // actually got updated, ie. is in use - ensure it get's a repaint
|
|
|
|
w->addLayerRepaint(entry->second);
|
|
|
|
}
|
|
|
|
}
|
2011-10-19 23:04:52 +00:00
|
|
|
|
|
|
|
void AnimationEffect::_windowClosed( EffectWindow* w )
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
[effects/dialogparent] Fix flickering of parent windows
Summary:
If a modal window is closed and some alternative effect that animates
the disappearing of windows is enabled(e.g. the Glide effect, or the
Scale effect), the Dialog Parent effect can cause flickering of the
parent window because its animation duration doesn't match duration of
those alternative effects.
Also, if the Fade effect, the Glide effect, and the Scale effect are
disabled, the Dialog Parent will keep the parent window alive for no
good reason.
This change addresses that problem by adding keepAlive property to
`animate` function so scripted effects have more control over lifetime
of animated windows.
If both a modal window and its parent window are closed at the same time
(and there is no effect that animates the disappearing of windows), the
Dialog Parent will stop immediately(because windowDeleted will be
emitted right after windowClosed signal).
If both a modal window and its parent window are closed at the same time
(and there is effect that animates the disappearing of windows), the
Dialog Parent won't reference the latter window. Thus, it won't cause
flickering. I.e. it will "passively" animate parent windows.
BUG: 355036
FIXED-IN: 5.15.0
Reviewers: #kwin, davidedmundson
Reviewed By: #kwin, davidedmundson
Subscribers: davidedmundson, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D14919
2018-10-10 07:36:45 +00:00
|
|
|
|
|
|
|
auto it = d->m_animations.find(w);
|
|
|
|
if (it == d->m_animations.end()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
KeepAliveLockPtr keepAliveLock;
|
|
|
|
|
|
|
|
QList<AniData> &animations = (*it).first;
|
|
|
|
for (auto animationIt = animations.begin();
|
|
|
|
animationIt != animations.end();
|
|
|
|
++animationIt) {
|
|
|
|
if (!(*animationIt).keepAlive) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (keepAliveLock.isNull()) {
|
|
|
|
keepAliveLock = KeepAliveLockPtr::create(w);
|
|
|
|
}
|
|
|
|
|
|
|
|
(*animationIt).keepAliveLock = keepAliveLock;
|
2011-10-19 23:04:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AnimationEffect::_windowDeleted( EffectWindow* w )
|
|
|
|
{
|
|
|
|
Q_D(AnimationEffect);
|
|
|
|
d->m_animations.remove( w );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-10-27 21:35:19 +00:00
|
|
|
QString AnimationEffect::debug(const QString &/*parameter*/) const
|
|
|
|
{
|
|
|
|
Q_D(const AnimationEffect);
|
|
|
|
QString dbg;
|
|
|
|
if (d->m_animations.isEmpty())
|
2013-07-23 05:02:52 +00:00
|
|
|
dbg = QStringLiteral("No window is animated");
|
2012-10-27 21:35:19 +00:00
|
|
|
else {
|
|
|
|
AniMap::const_iterator entry = d->m_animations.constBegin(), mapEnd = d->m_animations.constEnd();
|
|
|
|
for (; entry != mapEnd; ++entry) {
|
2013-07-23 05:02:52 +00:00
|
|
|
QString caption = entry.key()->isDeleted() ? QStringLiteral("[Deleted]") : entry.key()->caption();
|
2012-10-27 21:35:19 +00:00
|
|
|
if (caption.isEmpty())
|
2013-07-23 05:02:52 +00:00
|
|
|
caption = QStringLiteral("[Untitled]");
|
2015-11-05 14:14:06 +00:00
|
|
|
dbg += QLatin1String("Animating window: ") + caption + QLatin1Char('\n');
|
2012-10-27 21:35:19 +00:00
|
|
|
QList<AniData>::const_iterator anim = entry->first.constBegin(), animEnd = entry->first.constEnd();
|
|
|
|
for (; anim != animEnd; ++anim)
|
|
|
|
dbg += anim->debugInfo();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return dbg;
|
|
|
|
}
|
|
|
|
|
2018-07-23 21:52:58 +00:00
|
|
|
AnimationEffect::AniMap AnimationEffect::state() const
|
|
|
|
{
|
|
|
|
Q_D(const AnimationEffect);
|
|
|
|
return d->m_animations;
|
|
|
|
}
|
|
|
|
|
2011-10-19 23:04:52 +00:00
|
|
|
#include "moc_kwinanimationeffect.cpp"
|