diff --git a/autotests/test_builtin_effectloader.cpp b/autotests/test_builtin_effectloader.cpp --- a/autotests/test_builtin_effectloader.cpp +++ b/autotests/test_builtin_effectloader.cpp @@ -103,6 +103,7 @@ QTest::newRow("MouseMark") << QStringLiteral("mousemark") << true; QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << true; QTest::newRow("Resize") << QStringLiteral("resize") << true; + QTest::newRow("Scale") << QStringLiteral("scale") << true; QTest::newRow("ScreenEdge") << QStringLiteral("screenedge") << true; QTest::newRow("ScreenShot") << QStringLiteral("screenshot") << true; QTest::newRow("Sheet") << QStringLiteral("sheet") << true; @@ -159,6 +160,7 @@ << QStringLiteral("mousemark") << QStringLiteral("presentwindows") << QStringLiteral("resize") + << QStringLiteral("scale") << QStringLiteral("screenedge") << QStringLiteral("screenshot") << QStringLiteral("sheet") @@ -237,6 +239,7 @@ QTest::newRow("MouseMark") << QStringLiteral("mousemark") << true << xc << true; QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << true << xc << true; QTest::newRow("Resize") << QStringLiteral("resize") << true << xc << true; + QTest::newRow("Scale") << QStringLiteral("scale") << true << xc << true; QTest::newRow("ScreenEdge") << QStringLiteral("screenedge") << true << xc << true; QTest::newRow("ScreenShot") << QStringLiteral("screenshot") << true << xc << true; QTest::newRow("Sheet") << QStringLiteral("sheet") << false << xc << true; @@ -325,6 +328,7 @@ QTest::newRow("MouseMark") << QStringLiteral("mousemark") << true << xc; QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << true << xc; QTest::newRow("Resize") << QStringLiteral("resize") << true << xc; + QTest::newRow("Scale") << QStringLiteral("scale") << true << xc; QTest::newRow("ScreenEdge") << QStringLiteral("screenedge") << true << xc; QTest::newRow("ScreenShot") << QStringLiteral("screenshot") << true << xc; QTest::newRow("Sheet") << QStringLiteral("sheet") << false << xc; diff --git a/autotests/test_plugin_effectloader.cpp b/autotests/test_plugin_effectloader.cpp --- a/autotests/test_plugin_effectloader.cpp +++ b/autotests/test_plugin_effectloader.cpp @@ -95,6 +95,7 @@ QTest::newRow("MouseMark") << QStringLiteral("mousemark") << false; QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << false; QTest::newRow("Resize") << QStringLiteral("resize") << false; + QTest::newRow("Scale") << QStringLiteral("scale") << false; QTest::newRow("ScreenEdge") << QStringLiteral("screenedge") << false; QTest::newRow("ScreenShot") << QStringLiteral("screenshot") << false; QTest::newRow("Sheet") << QStringLiteral("sheet") << false; diff --git a/autotests/test_scripted_effectloader.cpp b/autotests/test_scripted_effectloader.cpp --- a/autotests/test_scripted_effectloader.cpp +++ b/autotests/test_scripted_effectloader.cpp @@ -125,6 +125,7 @@ QTest::newRow("MouseMark") << QStringLiteral("mousemark") << false; QTest::newRow("PresentWindows") << QStringLiteral("presentwindows") << false; QTest::newRow("Resize") << QStringLiteral("resize") << false; + QTest::newRow("Scale") << QStringLiteral("scale") << false; QTest::newRow("ScreenEdge") << QStringLiteral("screenedge") << false; QTest::newRow("ScreenShot") << QStringLiteral("screenshot") << false; QTest::newRow("Sheet") << QStringLiteral("sheet") << false; diff --git a/effects/CMakeLists.txt b/effects/CMakeLists.txt --- a/effects/CMakeLists.txt +++ b/effects/CMakeLists.txt @@ -90,6 +90,7 @@ presentwindows/presentwindows.cpp presentwindows/presentwindows_proxy.cpp resize/resize.cpp + scale/scale.cpp showfps/showfps.cpp slide/slide.cpp thumbnailaside/thumbnailaside.cpp @@ -119,6 +120,7 @@ mousemark/mousemarkconfig.kcfgc presentwindows/presentwindowsconfig.kcfgc resize/resizeconfig.kcfgc + scale/scaleconfig.kcfgc showfps/showfpsconfig.kcfgc slide/slideconfig.kcfgc slidingpopups/slidingpopupsconfig.kcfgc @@ -179,6 +181,7 @@ add_subdirectory( magnifier ) add_subdirectory( mouseclick ) add_subdirectory( mousemark ) +add_subdirectory( scale ) include( screenshot/CMakeLists.txt ) include( sheet/CMakeLists.txt ) include( snaphelper/CMakeLists.txt ) diff --git a/effects/effect_builtins.h b/effects/effect_builtins.h --- a/effects/effect_builtins.h +++ b/effects/effect_builtins.h @@ -57,6 +57,7 @@ MouseMark, PresentWindows, Resize, + Scale, ScreenEdge, ScreenShot, Sheet, diff --git a/effects/effect_builtins.cpp b/effects/effect_builtins.cpp --- a/effects/effect_builtins.cpp +++ b/effects/effect_builtins.cpp @@ -37,6 +37,7 @@ #include "magiclamp/magiclamp.h" #include "minimizeanimation/minimizeanimation.h" #include "resize/resize.h" +#include "scale/scale.h" #include "showfps/showfps.h" #include "showpaint/showpaint.h" #include "slide/slide.h" @@ -441,6 +442,21 @@ nullptr, nullptr #endif +EFFECT_FALLBACK + }, { + QStringLiteral("scale"), + i18ndc("kwin_effects", "Name of a KWin Effect", "Scale"), + i18ndc("kwin_effects", "Comment describing the KWin Effect", "Make windows smoothly scale in and out when they are shown or hidden"), + QStringLiteral("Appearance"), + QString(), + QUrl(), + false, + false, +#ifdef EFFECT_BUILTINS + &createHelper, + &ScaleEffect::supported, + nullptr +#endif EFFECT_FALLBACK }, { QStringLiteral("screenedge"), diff --git a/effects/scale/CMakeLists.txt b/effects/scale/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/effects/scale/CMakeLists.txt @@ -0,0 +1,25 @@ +####################################### +# Config +set(kwin_scale_config_SRCS scale_config.cpp) +ki18n_wrap_ui(kwin_scale_config_SRCS scale_config.ui) +qt5_add_dbus_interface(kwin_scale_config_SRCS ${kwin_effects_dbus_xml} kwineffects_interface) +kconfig_add_kcfg_files(kwin_scale_config_SRCS scaleconfig.kcfgc) + +add_library(kwin_scale_config MODULE ${kwin_scale_config_SRCS}) + +target_link_libraries(kwin_scale_config + Qt5::DBus + KF5::ConfigWidgets + KF5::I18n + KF5::Service +) + +kcoreaddons_desktop_to_json(kwin_scale_config scale_config.desktop SERVICE_TYPES kcmodule.desktop) + +install( + TARGETS + kwin_scale_config + DESTINATION + ${PLUGIN_INSTALL_DIR}/kwin/effects/configs +) + diff --git a/effects/scale/scale.h b/effects/scale/scale.h new file mode 100644 --- /dev/null +++ b/effects/scale/scale.h @@ -0,0 +1,116 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + + Copyright (C) 2018 Vlad Zagorodniy + +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 . +*********************************************************************/ + +#ifndef KWIN_SCALE_H +#define KWIN_SCALE_H + +// kwineffects +#include + +namespace KWin +{ + +class ScaleEffect : public Effect +{ + Q_OBJECT + Q_PROPERTY(int duration READ duration) + Q_PROPERTY(qreal inScale READ inScale) + Q_PROPERTY(qreal inOpacity READ inOpacity) + Q_PROPERTY(qreal outScale READ outScale) + Q_PROPERTY(qreal outOpacity READ outOpacity) + +public: + ScaleEffect(); + ~ScaleEffect() override; + + 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; + + static bool supported(); + + int duration() const; + qreal inScale() const; + qreal inOpacity() const; + qreal outScale() const; + qreal outOpacity() const; + +private Q_SLOTS: + void windowAdded(EffectWindow *w); + void windowClosed(EffectWindow *w); + void windowDeleted(EffectWindow *w); + void windowDataChanged(EffectWindow *w, int role); + +private: + bool isScaleWindow(EffectWindow *w) const; + + std::chrono::milliseconds m_duration; + QHash m_animations; + + struct ScaleParams { + struct { + qreal from; + qreal to; + } scale, opacity; + }; + + ScaleParams m_inParams; + ScaleParams m_outParams; +}; + +inline int ScaleEffect::requestedEffectChainPosition() const +{ + return 50; +} + +inline int ScaleEffect::duration() const +{ + return m_duration.count(); +} + +inline qreal ScaleEffect::inScale() const +{ + return m_inParams.scale.from; +} + +inline qreal ScaleEffect::inOpacity() const +{ + return m_inParams.opacity.from; +} + +inline qreal ScaleEffect::outScale() const +{ + return m_outParams.scale.to; +} + +inline qreal ScaleEffect::outOpacity() const +{ + return m_outParams.opacity.to; +} + +} // namespace KWin + +#endif diff --git a/effects/scale/scale.cpp b/effects/scale/scale.cpp new file mode 100644 --- /dev/null +++ b/effects/scale/scale.cpp @@ -0,0 +1,284 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + + Copyright (C) 2018 Vlad Zagorodniy + +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 . +*********************************************************************/ + +// own +#include "scale.h" + +// KConfigSkeleton +#include "scaleconfig.h" + +// Qt +#include + +namespace KWin +{ + +static const QSet s_blacklist { + // The logout screen has to be animated only by the logout effect. + QStringLiteral("ksmserver ksmserver"), + + // KDE Plasma splash screen has to be animated only by the login effect. + QStringLiteral("ksplashqml ksplashqml"), + QStringLiteral("ksplashsimple ksplashsimple"), + QStringLiteral("ksplashx ksplashx") +}; + +ScaleEffect::ScaleEffect() +{ + initConfig(); + reconfigure(ReconfigureAll); + + connect(effects, &EffectsHandler::windowAdded, this, &ScaleEffect::windowAdded); + connect(effects, &EffectsHandler::windowClosed, this, &ScaleEffect::windowClosed); + connect(effects, &EffectsHandler::windowDeleted, this, &ScaleEffect::windowDeleted); + connect(effects, &EffectsHandler::windowDataChanged, this, &ScaleEffect::windowDataChanged); +} + +ScaleEffect::~ScaleEffect() +{ +} + +void ScaleEffect::reconfigure(ReconfigureFlags flags) +{ + Q_UNUSED(flags) + + ScaleConfig::self()->read(); + m_duration = std::chrono::milliseconds(animationTime(160)); + + m_inParams.scale.from = ScaleConfig::inScale(); + m_inParams.scale.to = 1.0; + m_inParams.opacity.from = ScaleConfig::inOpacity(); + m_inParams.opacity.to = 1.0; + + m_outParams.scale.from = 1.0; + m_outParams.scale.to = ScaleConfig::outScale(); + m_outParams.opacity.from = 1.0; + m_outParams.opacity.to = ScaleConfig::outOpacity(); +} + +void ScaleEffect::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; + } + + effects->prePaintScreen(data, time); +} + +void ScaleEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, int time) +{ + if (m_animations.contains(w)) { + data.setTransformed(); + w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DELETE); + } + + effects->prePaintWindow(w, data, time); +} + +void ScaleEffect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) +{ + auto animationIt = m_animations.constFind(w); + if (animationIt == m_animations.constEnd()) { + effects->paintWindow(w, mask, region, data); + return; + } + + const ScaleParams params = w->isDeleted() ? m_outParams : m_inParams; + const qreal t = (*animationIt).value(); + const qreal scale = interpolate(params.scale.from, params.scale.to, t); + + data.setXScale(scale); + data.setYScale(scale); + data.setXTranslation(0.5 * (1.0 - scale) * w->width()); + data.setYTranslation(0.5 * (1.0 - scale) * w->height()); + data.multiplyOpacity(interpolate(params.opacity.from, params.opacity.to, t)); + + effects->paintWindow(w, mask, region, data); +} + +void ScaleEffect::postPaintScreen() +{ + auto animationIt = m_animations.begin(); + while (animationIt != m_animations.end()) { + EffectWindow *w = animationIt.key(); + + const QRect geo = w->expandedGeometry(); + const ScaleParams params = w->isDeleted() ? m_outParams : m_inParams; + const qreal scale = qMax(params.scale.from, params.scale.to); + const QRect repaintRect( + geo.topLeft() + 0.5 * (1.0 - scale) * QPoint(geo.width(), geo.height()), + geo.size() * scale); + effects->addRepaint(repaintRect); + + if ((*animationIt).done()) { + if (w->isDeleted()) { + w->unrefWindow(); + } else { + w->setData(WindowForceBackgroundContrastRole, QVariant()); + w->setData(WindowForceBlurRole, QVariant()); + } + animationIt = m_animations.erase(animationIt); + } else { + ++animationIt; + } + } + + effects->postPaintScreen(); +} + +bool ScaleEffect::isActive() const +{ + return !m_animations.isEmpty(); +} + +bool ScaleEffect::supported() +{ + return effects->animationsSupported(); +} + +void ScaleEffect::windowAdded(EffectWindow *w) +{ + if (effects->activeFullScreenEffect()) { + return; + } + + if (!isScaleWindow(w)) { + return; + } + + if (!w->isVisible()) { + return; + } + + const void *addGrab = w->data(WindowAddedGrabRole).value(); + if (addGrab && addGrab != this) { + return; + } + + TimeLine &timeLine = m_animations[w]; + timeLine.reset(); + timeLine.setDirection(TimeLine::Forward); + timeLine.setDuration(m_duration); + timeLine.setEasingCurve(QEasingCurve::InCurve); + + w->setData(WindowAddedGrabRole, QVariant::fromValue(static_cast(this))); + w->setData(WindowForceBackgroundContrastRole, QVariant(true)); + w->setData(WindowForceBlurRole, QVariant(true)); + + w->addRepaintFull(); +} + +void ScaleEffect::windowClosed(EffectWindow *w) +{ + if (effects->activeFullScreenEffect()) { + return; + } + + if (!isScaleWindow(w)) { + return; + } + + if (!w->isVisible()) { + return; + } + + const void *closeGrab = w->data(WindowClosedGrabRole).value(); + if (closeGrab && closeGrab != this) { + return; + } + + w->refWindow(); + + TimeLine &timeLine = m_animations[w]; + timeLine.reset(); + timeLine.setDirection(TimeLine::Forward); + timeLine.setDuration(m_duration); + timeLine.setEasingCurve(QEasingCurve::OutCurve); + + w->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast(this))); + w->setData(WindowForceBackgroundContrastRole, QVariant(true)); + w->setData(WindowForceBlurRole, QVariant(true)); + + w->addRepaintFull(); +} + +void ScaleEffect::windowDeleted(EffectWindow *w) +{ + m_animations.remove(w); +} + +void ScaleEffect::windowDataChanged(EffectWindow *w, int role) +{ + if (role != WindowAddedGrabRole && role != WindowClosedGrabRole) { + return; + } + + if (w->data(role).value() == this) { + return; + } + + auto animationIt = m_animations.find(w); + if (animationIt == m_animations.end()) { + return; + } + + if (w->isDeleted() && role == WindowClosedGrabRole) { + w->unrefWindow(); + } + + m_animations.erase(animationIt); + + w->setData(WindowForceBackgroundContrastRole, QVariant()); + w->setData(WindowForceBlurRole, QVariant()); +} + +bool ScaleEffect::isScaleWindow(EffectWindow *w) const +{ + // We don't want to animate most of plasmashell's windows, yet, some + // of them we want to, for example, Task Manager Settings window. + // The problem is that all those window share single window class. + // So, the only way to decide whether a window should be animated is + // to use a heuristic: if a window has decoration, then it's most + // likely a dialog or a settings window so we have to animate it. + if (w->windowClass() == QLatin1String("plasmashell plasmashell")) { + return w->hasDecoration(); + } + + if (s_blacklist.contains(w->windowClass())) { + return false; + } + + if (w->hasDecoration()) { + return true; + } + + if (!w->isManaged()) { + return false; + } + + return w->isNormalWindow() + || w->isDialog(); +} + +} // namespace KWin diff --git a/effects/scale/scale.kcfg b/effects/scale/scale.kcfg new file mode 100644 --- /dev/null +++ b/effects/scale/scale.kcfg @@ -0,0 +1,28 @@ + + + + + + 0 + + + 0.96 + + + 0.4 + 0.0 + 1.0 + + + 0.96 + + + 0.0 + 0.0 + 1.0 + + + diff --git a/effects/scale/scale_config.h b/effects/scale/scale_config.h new file mode 100644 --- /dev/null +++ b/effects/scale/scale_config.h @@ -0,0 +1,48 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + + Copyright (C) 2018 Vlad Zagorodniy + +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 . +*********************************************************************/ + +#ifndef SCALE_CONFIG_H +#define SCALE_CONFIG_H + +#include "ui_scale_config.h" + +#include + +namespace KWin +{ + +class ScaleEffectConfig : public KCModule +{ + Q_OBJECT + +public: + explicit ScaleEffectConfig(QWidget *parent = nullptr, const QVariantList &args = QVariantList()); + ~ScaleEffectConfig() override; + + void save() override; + +private: + ::Ui::ScaleEffectConfig ui; +}; + +} // namespace KWin + +#endif + diff --git a/effects/scale/scale_config.cpp b/effects/scale/scale_config.cpp new file mode 100644 --- /dev/null +++ b/effects/scale/scale_config.cpp @@ -0,0 +1,62 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + + Copyright (C) 2018 Vlad Zagorodniy + +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 . +*********************************************************************/ + +#include "scale_config.h" + +// KConfigSkeleton +#include "scaleconfig.h" +#include + +#include +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON(ScaleEffectConfigFactory, + "scale_config.json", + registerPlugin();) + +namespace KWin +{ + +ScaleEffectConfig::ScaleEffectConfig(QWidget *parent, const QVariantList &args) + : KCModule(KAboutData::pluginData(QStringLiteral("scale")), parent, args) +{ + ui.setupUi(this); + ScaleConfig::instance(KWIN_CONFIG); + addConfig(ScaleConfig::self(), this); + load(); +} + +ScaleEffectConfig::~ScaleEffectConfig() +{ +} + +void ScaleEffectConfig::save() +{ + KCModule::save(); + OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"), + QStringLiteral("/Effects"), + QDBusConnection::sessionBus()); + interface.reconfigureEffect(QStringLiteral("scale")); +} + +} // namespace KWin + +#include "scale_config.moc" diff --git a/effects/scale/scale_config.desktop b/effects/scale/scale_config.desktop new file mode 100644 --- /dev/null +++ b/effects/scale/scale_config.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kwin_scale_config +X-KDE-ParentComponents=scale + +Name=Scale diff --git a/effects/scale/scale_config.ui b/effects/scale/scale_config.ui new file mode 100644 --- /dev/null +++ b/effects/scale/scale_config.ui @@ -0,0 +1,93 @@ + + + ScaleEffectConfig + + + + 0 + 0 + 455 + 177 + + + + + + + Duration: + + + + + + + + 0 + 0 + + + + Default + + + milliseconds + + + 9999 + + + 5 + + + + + + + Window open scale: + + + + + + + Window close scale: + + + + + + + + 0 + 0 + + + + 9.990000000000000 + + + 0.050000000000000 + + + + + + + + 0 + 0 + + + + 9.990000000000000 + + + 0.050000000000000 + + + + + + + + diff --git a/effects/scale/scaleconfig.kcfgc b/effects/scale/scaleconfig.kcfgc new file mode 100644 --- /dev/null +++ b/effects/scale/scaleconfig.kcfgc @@ -0,0 +1,5 @@ +File=scale.kcfg +ClassName=ScaleConfig +NameSpace=KWin +Singleton=true +Mutators=true