[effects] Re-implement the Minimize Animation effect in JavaScript

Summary:
There were several reasons to rewrite the Minimize Animation effect in
JavaScript: to simplify code and to get rid of full repaints. One could
say that nothing prevents us from calculating the dirty region in
postPaintScreen or postPaintWindow and it is correct, but with the
scripting effects API the dirty region will be calculated for us, so we
can focus more on "what we want" instead of "how".

Visually, the "old" effect and the rewritten one look quite the same.
Except one tiny bit: if a window doesn't have an icon in the task manager,
it won't be animated. The reason for that is the purpose of this effect is
to show where the window will be after it's minimized, if the window
doesn't have icon in the task manager, one can't click at the center of
the screen to unminimize the window.

There is one significant change, the name of the effect was changed to
"Squash". If we put this effect and the Magic lamp effect under "Window
Minimize Animation" category (or if we add some "heading" label), then
the old name and the name of the category would "conflict". The new name
was suggested by Nate Graham and it very closely describes what the
effect does. "Scale" doesn't fit this effect because while a window is
being animated, its aspect ratio is not preserved.

Reviewers: #kwin, #plasma, davidedmundson

Reviewed By: #kwin, #plasma, davidedmundson

Subscribers: davidedmundson, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D16372
master
Vlad Zagorodniy 6 years ago
parent c32ffcab46
commit eb78b1ca3a

@ -99,7 +99,6 @@ void TestBuiltInEffectLoader::testHasEffect_data()
QTest::newRow("LookingGlass") << QStringLiteral("lookingglass") << true;
QTest::newRow("MagicLamp") << QStringLiteral("magiclamp") << true;
QTest::newRow("Magnifier") << QStringLiteral("magnifier") << true;
QTest::newRow("MinimizeAnimation") << QStringLiteral("minimizeanimation") << true;
QTest::newRow("MouseClick") << QStringLiteral("mouseclick") << true;
QTest::newRow("MouseMark") << QStringLiteral("mousemark") << true;
QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << true;
@ -156,7 +155,6 @@ void TestBuiltInEffectLoader::testKnownEffects()
<< QStringLiteral("lookingglass")
<< QStringLiteral("magiclamp")
<< QStringLiteral("magnifier")
<< QStringLiteral("minimizeanimation")
<< QStringLiteral("mouseclick")
<< QStringLiteral("mousemark")
<< QStringLiteral("presentwindows")
@ -235,7 +233,6 @@ void TestBuiltInEffectLoader::testSupported_data()
QTest::newRow("MagicLamp-GL") << QStringLiteral("magiclamp") << true << oc << true;
QTest::newRow("MagicLamp-GL-no-anim") << QStringLiteral("magiclamp") << false << oc << false;
QTest::newRow("Magnifier") << QStringLiteral("magnifier") << true << xc << true;
QTest::newRow("MinimizeAnimation") << QStringLiteral("minimizeanimation") << true << xc << true;
QTest::newRow("MouseClick") << QStringLiteral("mouseclick") << true << xc << true;
QTest::newRow("MouseMark") << QStringLiteral("mousemark") << true << xc << true;
QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << true << xc << true;
@ -324,7 +321,6 @@ void TestBuiltInEffectLoader::testLoadEffect_data()
QTest::newRow("MagicLamp") << QStringLiteral("magiclamp") << false << xc;
QTest::newRow("MagicLamp-GL") << QStringLiteral("magiclamp") << true << oc;
QTest::newRow("Magnifier") << QStringLiteral("magnifier") << true << xc;
QTest::newRow("MinimizeAnimation") << QStringLiteral("minimizeanimation") << true << xc;
QTest::newRow("MouseClick") << QStringLiteral("mouseclick") << true << xc;
QTest::newRow("MouseMark") << QStringLiteral("mousemark") << true << xc;
QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << true << xc;
@ -509,7 +505,6 @@ void TestBuiltInEffectLoader::testLoadAllEffects()
plugins.writeEntry(QStringLiteral("desktopgridEnabled"), false);
plugins.writeEntry(QStringLiteral("highlightwindowEnabled"), false);
plugins.writeEntry(QStringLiteral("kscreenEnabled"), false);
plugins.writeEntry(QStringLiteral("minimizeanimationEnabled"), false);
plugins.writeEntry(QStringLiteral("presentwindowsEnabled"), false);
plugins.writeEntry(QStringLiteral("screenedgeEnabled"), false);
plugins.writeEntry(QStringLiteral("screenshotEnabled"), false);

@ -90,7 +90,6 @@ void TestPluginEffectLoader::testHasEffect_data()
QTest::newRow("LookingGlass") << QStringLiteral("lookingglass") << false;
QTest::newRow("MagicLamp") << QStringLiteral("magiclamp") << false;
QTest::newRow("Magnifier") << QStringLiteral("magnifier") << false;
QTest::newRow("MinimizeAnimation") << QStringLiteral("minimizeanimation") << false;
QTest::newRow("MouseClick") << QStringLiteral("mouseclick") << false;
QTest::newRow("MouseMark") << QStringLiteral("mousemark") << false;
QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << false;
@ -119,6 +118,7 @@ void TestPluginEffectLoader::testHasEffect_data()
QTest::newRow("Login") << QStringLiteral("kwin4_effect_login") << false;
QTest::newRow("Logout") << QStringLiteral("kwin4_effect_logout") << false;
QTest::newRow("Maximize") << QStringLiteral("kwin4_effect_maximize") << false;
QTest::newRow("Squash") << QStringLiteral("kwin4_effect_squash") << false;
QTest::newRow("Translucency") << QStringLiteral("kwin4_effect_translucency") << false;
// and the fake effects we use here
QTest::newRow("fakeeffectplugin") << QStringLiteral("fakeeffectplugin") << true;

@ -120,7 +120,6 @@ void TestScriptedEffectLoader::testHasEffect_data()
QTest::newRow("LookingGlass") << QStringLiteral("lookingglass") << false;
QTest::newRow("MagicLamp") << QStringLiteral("magiclamp") << false;
QTest::newRow("Magnifier") << QStringLiteral("magnifier") << false;
QTest::newRow("MinimizeAnimation") << QStringLiteral("minimizeanimation") << false;
QTest::newRow("MouseClick") << QStringLiteral("mouseclick") << false;
QTest::newRow("MouseMark") << QStringLiteral("mousemark") << false;
QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << false;
@ -151,6 +150,7 @@ void TestScriptedEffectLoader::testHasEffect_data()
QTest::newRow("Login") << QStringLiteral("kwin4_effect_login") << true;
QTest::newRow("Logout") << QStringLiteral("kwin4_effect_logout") << true;
QTest::newRow("Maximize") << QStringLiteral("kwin4_effect_maximize") << true;
QTest::newRow("Squash") << QStringLiteral("kwin4_effect_squash") << true;
QTest::newRow("Translucency") << QStringLiteral("kwin4_effect_translucency") << true;
}
@ -182,6 +182,7 @@ void TestScriptedEffectLoader::testKnownEffects()
<< QStringLiteral("kwin4_effect_login")
<< QStringLiteral("kwin4_effect_logout")
<< QStringLiteral("kwin4_effect_maximize")
<< QStringLiteral("kwin4_effect_squash")
<< QStringLiteral("kwin4_effect_translucency");
KWin::ScriptedEffectLoader loader;
@ -208,6 +209,7 @@ void TestScriptedEffectLoader::testLoadEffect_data()
QTest::newRow("Login") << QStringLiteral("kwin4_effect_login") << true;
QTest::newRow("Logout") << QStringLiteral("kwin4_effect_logout") << true;
QTest::newRow("Maximize") << QStringLiteral("kwin4_effect_maximize") << true;
QTest::newRow("Squash") << QStringLiteral("kwin4_effect_squash") << true;
QTest::newRow("Translucency") << QStringLiteral("kwin4_effect_translucency") << true;
}
@ -359,7 +361,7 @@ void TestScriptedEffectLoader::testLoadAllEffects()
plugins.writeEntry(kwin4 + QStringLiteral("loginEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("logoutEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("maximizeEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("minimizeanimationEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("squashEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("translucencyEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("eyeonscreenEnabled"), false);
plugins.writeEntry(kwin4 + QStringLiteral("windowapertureEnabled"), false);

@ -142,6 +142,7 @@ add_subdirectory( login )
add_subdirectory( logout )
add_subdirectory( maximize )
add_subdirectory( morphingpopups )
add_subdirectory( squash )
add_subdirectory( translucency )
add_subdirectory( windowaperture )
@ -156,7 +157,6 @@ include( fallapart/CMakeLists.txt )
include( highlightwindow/CMakeLists.txt )
include( kscreen/CMakeLists.txt )
add_subdirectory( magiclamp )
include( minimizeanimation/CMakeLists.txt )
add_subdirectory( presentwindows )
add_subdirectory( resize )
include( screenedge/CMakeLists.txt )

@ -35,7 +35,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "fallapart/fallapart.h"
#include "highlightwindow/highlightwindow.h"
#include "magiclamp/magiclamp.h"
#include "minimizeanimation/minimizeanimation.h"
#include "resize/resize.h"
#include "scale/scale.h"
#include "showfps/showfps.h"
@ -369,21 +368,6 @@ EFFECT_FALLBACK
&MagnifierEffect::supported,
nullptr
#endif
EFFECT_FALLBACK
}, {
QStringLiteral("minimizeanimation"),
i18ndc("kwin_effects", "Name of a KWin Effect", "Minimize Animation"),
i18ndc("kwin_effects", "Comment describing the KWin Effect", "Animate the minimizing of windows"),
QStringLiteral("Appearance"),
QStringLiteral("minimize"),
QUrl(QStringLiteral("http://files.kde.org/plasma/kwin/effect-videos/minimize.ogv")),
true,
false,
#ifdef EFFECT_BUILTINS
&createHelper<MinimizeAnimationEffect>,
&MinimizeAnimationEffect::supported,
nullptr
#endif
EFFECT_FALLBACK
}, {
QStringLiteral("mouseclick"),

@ -52,7 +52,6 @@ enum class BuiltInEffect
LookingGlass,
MagicLamp,
Magnifier,
MinimizeAnimation,
MouseClick,
MouseMark,
PresentWindows,

@ -1,7 +0,0 @@
#######################################
# Effect
# Source files
set( kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources}
minimizeanimation/minimizeanimation.cpp
)

@ -1,165 +0,0 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2007 Rivo Laks <rivolaks@hot.ee>
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 "minimizeanimation.h"
#include <QVector2D>
namespace KWin
{
MinimizeAnimationEffect::MinimizeAnimationEffect()
{
reconfigure(ReconfigureAll);
connect(effects, &EffectsHandler::windowDeleted, this, &MinimizeAnimationEffect::windowDeleted);
connect(effects, &EffectsHandler::windowMinimized, this, &MinimizeAnimationEffect::windowMinimized);
connect(effects, &EffectsHandler::windowUnminimized, this, &MinimizeAnimationEffect::windowUnminimized);
}
void MinimizeAnimationEffect::reconfigure(ReconfigureFlags flags)
{
Q_UNUSED(flags)
m_duration = std::chrono::milliseconds(static_cast<int>(animationTime(250)));
}
bool MinimizeAnimationEffect::supported()
{
return effects->animationsSupported();
}
void MinimizeAnimationEffect::prePaintScreen(ScreenPrePaintData &data, int time)
{
const std::chrono::milliseconds delta(time);
auto animationIt = m_animations.begin();
while (animationIt != m_animations.end()) {
(*animationIt).update(delta);
++animationIt;
}
// We need to mark the screen windows as transformed. Otherwise the
// whole screen won't be repainted, resulting in artefacts.
data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
effects->prePaintScreen(data, time);
}
void MinimizeAnimationEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, int time)
{
if (m_animations.contains(w)) {
data.setTransformed();
w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE);
}
effects->prePaintWindow(w, data, time);
}
void MinimizeAnimationEffect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
{
const auto animationIt = m_animations.constFind(w);
if (animationIt != m_animations.constEnd()) {
// 0 = not minimized, 1 = fully minimized
const qreal progress = (*animationIt).value();
QRect geo = w->geometry();
QRect icon = w->iconGeometry();
// If there's no icon geometry, minimize to the center of the screen
if (!icon.isValid()) {
icon = QRect(effects->virtualScreenGeometry().center(), QSize(0, 0));
}
data *= QVector2D(interpolate(1.0, icon.width() / (double)geo.width(), progress),
interpolate(1.0, icon.height() / (double)geo.height(), progress));
data.setXTranslation(interpolate(data.xTranslation(), icon.x() - geo.x(), progress));
data.setYTranslation(interpolate(data.yTranslation(), icon.y() - geo.y(), progress));
data.multiplyOpacity(interpolate(1.0, 0.1, progress));
}
effects->paintWindow(w, mask, region, data);
}
void MinimizeAnimationEffect::postPaintScreen()
{
auto animationIt = m_animations.begin();
while (animationIt != m_animations.end()) {
if ((*animationIt).done()) {
animationIt = m_animations.erase(animationIt);
} else {
++animationIt;
}
}
effects->addRepaintFull();
effects->postPaintScreen();
}
void MinimizeAnimationEffect::windowDeleted(EffectWindow *w)
{
m_animations.remove(w);
}
void MinimizeAnimationEffect::windowMinimized(EffectWindow *w)
{
if (effects->activeFullScreenEffect()) {
return;
}
TimeLine &timeLine = m_animations[w];
if (timeLine.running()) {
timeLine.toggleDirection();
} else {
timeLine.setDirection(TimeLine::Forward);
timeLine.setDuration(m_duration);
timeLine.setEasingCurve(QEasingCurve::InOutSine);
}
effects->addRepaintFull();
}
void MinimizeAnimationEffect::windowUnminimized(EffectWindow *w)
{
if (effects->activeFullScreenEffect()) {
return;
}
TimeLine &timeLine = m_animations[w];
if (timeLine.running()) {
timeLine.toggleDirection();
} else {
timeLine.setDirection(TimeLine::Backward);
timeLine.setDuration(m_duration);
timeLine.setEasingCurve(QEasingCurve::InOutSine);
}
effects->addRepaintFull();
}
bool MinimizeAnimationEffect::isActive() const
{
return !m_animations.isEmpty();
}
} // namespace

@ -1,66 +0,0 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2007 Rivo Laks <rivolaks@hot.ee>
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/>.
*********************************************************************/
#ifndef KWIN_MINIMIZEANIMATION_H
#define KWIN_MINIMIZEANIMATION_H
// Include with base class for effects.
#include <kwineffects.h>
namespace KWin
{
/**
* Animates minimize/unminimize
**/
class MinimizeAnimationEffect : public Effect
{
Q_OBJECT
public:
MinimizeAnimationEffect();
void reconfigure(ReconfigureFlags flags) override;
void prePaintScreen(ScreenPrePaintData &data, int time) override;
void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, int time) override;
void paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) override;
void postPaintScreen() override;
bool isActive() const override;
int requestedEffectChainPosition() const override {
return 50;
}
static bool supported();
private Q_SLOTS:
void windowDeleted(EffectWindow *w);
void windowMinimized(EffectWindow *w);
void windowUnminimized(EffectWindow *w);
private:
std::chrono::milliseconds m_duration;
QHash<const EffectWindow*, TimeLine> m_animations;
};
} // namespace
#endif

@ -0,0 +1 @@
add_subdirectory(package)

@ -0,0 +1,9 @@
install(DIRECTORY contents
DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME}/effects/kwin4_effect_squash)
install(FILES metadata.desktop
DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME}/effects/kwin4_effect_squash)
install(FILES metadata.desktop
DESTINATION ${SERVICES_INSTALL_DIR}/${KWIN_NAME}
RENAME kwin4_effect_squash.desktop)

@ -0,0 +1,150 @@
/********************************************************************
This file is part of the KDE project.
Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com>
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/>.
*********************************************************************/
var squashEffect = {
duration: animationTime(250),
loadConfig: function () {
"use strict";
squashEffect.duration = animationTime(250);
},
slotWindowMinimized: function (window) {
"use strict";
if (effects.hasActiveFullScreenEffect) {
return;
}
// If the window doesn't have an icon in the task manager,
// don't animate it.
var iconRect = window.iconGeometry;
if (iconRect.width == 0 || iconRect.height == 0) {
return;
}
if (window.unminimizeAnimation) {
// TODO: Try to reverse it instead.
cancel(window.unminimizeAnimation);
delete window.unminimizeAnimation;
}
var windowRect = window.geometry;
window.minimizeAnimation = animate({
window: window,
curve: QEasingCurve.InOutSine,
duration: squashEffect.duration,
animations: [
{
type: Effect.Size,
from: {
value1: windowRect.width,
value2: windowRect.height
},
to: {
value1: iconRect.width,
value2: iconRect.height
}
},
{
type: Effect.Translation,
from: {
value1: 0.0,
value2: 0.0
},
to: {
value1: iconRect.x - windowRect.x -
(windowRect.width - iconRect.width) / 2,
value2: iconRect.y - windowRect.y -
(windowRect.height - iconRect.height) / 2,
}
},
{
type: Effect.Opacity,
from: 1.0,
to: 0.0
}
]
});
},
slotWindowUnminimized: function (window) {
"use strict";
if (effects.hasActiveFullScreenEffect) {
return;
}
// If the window doesn't have an icon in the task manager,
// don't animate it.
var iconRect = window.iconGeometry;
if (iconRect.width == 0 || iconRect.height == 0) {
return;
}
if (window.minimizeAnimation) {
// TODO: Try to reverse it instead.
cancel(window.minimizeAnimation);
delete window.minimizeAnimation;
}
var windowRect = window.geometry;
window.unminimizeAnimation = animate({
window: window,
curve: QEasingCurve.InOutSine,
duration: squashEffect.duration,
animations: [
{
type: Effect.Size,
from: {
value1: iconRect.width,
value2: iconRect.height
},
to: {
value1: windowRect.width,
value2: windowRect.height
}
},
{
type: Effect.Translation,
from: {
value1: iconRect.x - windowRect.x -
(windowRect.width - iconRect.width) / 2,
value2: iconRect.y - windowRect.y -
(windowRect.height - iconRect.height) / 2,
},
to: {
value1: 0.0,
value2: 0.0
}
},
{
type: Effect.Opacity,
from: 0.0,
to: 1.0
}
]
});
},
init: function () {
"use strict";
effect.configChanged.connect(squashEffect.loadConfig);
effects.windowMinimized.connect(squashEffect.slotWindowMinimized);
effects.windowUnminimized.connect(squashEffect.slotWindowUnminimized);
}
};
squashEffect.init();

@ -0,0 +1,21 @@
[Desktop Entry]
Comment=Squash windows when they are minimized
Icon=preferences-system-windows-effect-squash
Name=Squash
Type=Service
X-KDE-ParentApp=
X-KDE-PluginInfo-Author=Rivo Laks, Vlad Zagorodniy
X-KDE-PluginInfo-Category=Appearance
X-KDE-PluginInfo-Email=rivolaks@hot.ee, vladzzag@gmail.com
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-Name=kwin4_effect_squash
X-KDE-PluginInfo-Version=1
X-KDE-PluginInfo-Website=
X-KDE-ServiceTypes=KWin/Effect
X-KDE-PluginInfo-EnabledByDefault=true
X-KDE-Ordering=60
X-Plasma-API=javascript
X-Plasma-MainScript=code/main.js
X-KWin-Exclusive-Category=minimize
X-KWin-Video-Url=http://files.kde.org/plasma/kwin/effect-videos/minimize.ogv

@ -5,3 +5,9 @@ Id=replace-scalein-with-scale
File=kwinrc
Group=Plugins
Key=kwin4_effect_scaleinEnabled,scaleEnabled
# Port the Minimize Animation effect to JavaScript.
Id=port-minimizeanimation-effect-to-js
File=kwinrc
Group=Plugins
Key=minimizeanimationEnabled,kwin4_effect_squashEnabled

Loading…
Cancel
Save