diff --git a/abstract_client.h b/abstract_client.h --- a/abstract_client.h +++ b/abstract_client.h @@ -422,6 +422,7 @@ return m_minimized; } virtual void setFullScreen(bool set, bool user = true) = 0; + virtual QRect geometryFSRestore() const = 0; virtual TabGroup *tabGroup() const; Q_INVOKABLE virtual bool untab(const QRect &toGeometry = QRect(), bool clientRemoved = false); virtual bool isCurrentTab() const; @@ -832,6 +833,7 @@ int borderBottom() const; virtual void changeMaximize(bool horizontal, bool vertical, bool adjust) = 0; virtual void setGeometryRestore(const QRect &geo) = 0; + virtual void setGeometryFSRestore(const QRect &geo) = 0; /** * Called from move after updating the geometry. Can be reimplemented to perform specific tasks. * The base implementation does nothing. 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 @@ -91,6 +91,7 @@ QTest::newRow("DimScreen") << QStringLiteral("dimscreen") << true; QTest::newRow("FallApart") << QStringLiteral("fallapart") << true; QTest::newRow("FlipSwitch") << QStringLiteral("flipswitch") << true; + QTest::newRow("FullScreen") << QStringLiteral("fullscreen") << true; QTest::newRow("Glide") << QStringLiteral("glide") << true; QTest::newRow("HighlightWindow") << QStringLiteral("highlightwindow") << true; QTest::newRow("Invert") << QStringLiteral("invert") << true; @@ -147,6 +148,7 @@ << QStringLiteral("dimscreen") << QStringLiteral("fallapart") << QStringLiteral("flipswitch") + << QStringLiteral("fullscreen") << QStringLiteral("glide") << QStringLiteral("highlightwindow") << QStringLiteral("invert") @@ -219,6 +221,7 @@ QTest::newRow("FlipSwitch") << QStringLiteral("flipswitch") << false << xc << true; QTest::newRow("FlipSwitch-GL") << QStringLiteral("flipswitch") << true << oc << true; QTest::newRow("FlipSwitch-GL-no-anim") << QStringLiteral("flipswitch") << false << oc << false; + QTest::newRow("FullScreen") << QStringLiteral("fullscreen") << true << xc << true; QTest::newRow("Glide") << QStringLiteral("glide") << false << xc << true; QTest::newRow("Glide-GL") << QStringLiteral("glide") << true << oc << true; QTest::newRow("Glide-GL-no-anim") << QStringLiteral("glide") << false << oc << false; @@ -309,6 +312,7 @@ QTest::newRow("FallApart-GL") << QStringLiteral("fallapart") << true << oc; QTest::newRow("FlipSwitch") << QStringLiteral("flipswitch") << false << xc; QTest::newRow("FlipSwitch-GL") << QStringLiteral("flipswitch") << true << oc; + QTest::newRow("FullScreen") << QStringLiteral("fullscreen") << true << xc; QTest::newRow("Glide") << QStringLiteral("glide") << false << xc; QTest::newRow("Glide-GL") << QStringLiteral("glide") << true << oc; QTest::newRow("HighlightWindow") << QStringLiteral("highlightwindow") << true << xc; @@ -503,6 +507,7 @@ KConfigGroup plugins = config->group("Plugins"); plugins.writeEntry(QStringLiteral("desktopgridEnabled"), false); plugins.writeEntry(QStringLiteral("highlightwindowEnabled"), false); + plugins.writeEntry(QStringLiteral("fullscreenEnabled"), false); plugins.writeEntry(QStringLiteral("kscreenEnabled"), false); plugins.writeEntry(QStringLiteral("minimizeanimationEnabled"), false); plugins.writeEntry(QStringLiteral("presentwindowsEnabled"), false); 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 @@ -83,6 +83,7 @@ QTest::newRow("DimScreen") << QStringLiteral("dimscreen") << false; QTest::newRow("FallApart") << QStringLiteral("fallapart") << false; QTest::newRow("FlipSwitch") << QStringLiteral("flipswitch") << false; + QTest::newRow("FullScreen") << QStringLiteral("fullscreen") << false; QTest::newRow("Glide") << QStringLiteral("glide") << false; QTest::newRow("HighlightWindow") << QStringLiteral("highlightwindow") << false; QTest::newRow("Invert") << QStringLiteral("invert") << 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 @@ -112,6 +112,7 @@ QTest::newRow("DimScreen") << QStringLiteral("dimscreen") << false; QTest::newRow("FallApart") << QStringLiteral("fallapart") << false; QTest::newRow("FlipSwitch") << QStringLiteral("flipswitch") << false; + QTest::newRow("FullScreen") << QStringLiteral("fullscreen") << false; QTest::newRow("Glide") << QStringLiteral("glide") << false; QTest::newRow("HighlightWindow") << QStringLiteral("highlightwindow") << false; QTest::newRow("Invert") << QStringLiteral("invert") << false; diff --git a/client.h b/client.h --- a/client.h +++ b/client.h @@ -150,7 +150,7 @@ void setFullScreen(bool set, bool user = true) override; bool isFullScreen() const override; bool userCanSetFullScreen() const override; - QRect geometryFSRestore() const { + QRect geometryFSRestore() const override { return geom_fs_restore; // Only for session saving } int fullScreenMode() const { @@ -391,6 +391,7 @@ bool belongsToDesktop() const override; bool isActiveFullScreen() const override; void setGeometryRestore(const QRect &geo) override; + void setGeometryFSRestore(const QRect &geo) override; void updateTabGroupStates(TabGroup::States states) override; void doMove(int x, int y) override; bool doStartMoveResize() override; @@ -676,6 +677,11 @@ return fullscreen_mode != FullScreenNone; } +inline void Client::setGeometryFSRestore(const QRect &geo) +{ + geom_fs_restore = geo; +} + inline bool Client::hasNETSupport() const { return info->hasNETSupport(); diff --git a/effects.cpp b/effects.cpp --- a/effects.cpp +++ b/effects.cpp @@ -338,6 +338,15 @@ emit windowHidden(c->effectWindow()); } ); + connect(c, &AbstractClient::fullScreenChanged, this, + [this, c]() { + bool entered = c->isFullScreen(); + QRect oldGeometry = entered + ? c->geometryFSRestore() + : Workspace::self()->clientArea(FullScreenArea, c); + emit windowFullScreenChanged(c->effectWindow(), entered, oldGeometry); + } + ); } void EffectsHandlerImpl::setupClientConnections(Client* c) diff --git a/effects/CMakeLists.txt b/effects/CMakeLists.txt --- a/effects/CMakeLists.txt +++ b/effects/CMakeLists.txt @@ -151,6 +151,7 @@ add_subdirectory( diminactive ) include( dimscreen/CMakeLists.txt ) include( fallapart/CMakeLists.txt ) +include( fullscreen/CMakeLists.txt ) include( highlightwindow/CMakeLists.txt ) include( kscreen/CMakeLists.txt ) add_subdirectory( magiclamp ) diff --git a/effects/effect_builtins.h b/effects/effect_builtins.h --- a/effects/effect_builtins.h +++ b/effects/effect_builtins.h @@ -45,6 +45,7 @@ DimScreen, FallApart, FlipSwitch, + FullScreen, Glide, HighlightWindow, Invert, diff --git a/effects/effect_builtins.cpp b/effects/effect_builtins.cpp --- a/effects/effect_builtins.cpp +++ b/effects/effect_builtins.cpp @@ -33,6 +33,7 @@ #include "diminactive/diminactive.h" #include "dimscreen/dimscreen.h" #include "fallapart/fallapart.h" +#include "fullscreen/fullscreen.h" #include "highlightwindow/highlightwindow.h" #include "magiclamp/magiclamp.h" #include "minimizeanimation/minimizeanimation.h" @@ -261,6 +262,21 @@ &FlipSwitchEffect::supported, nullptr #endif +EFFECT_FALLBACK + }, { + QStringLiteral("fullscreen"), + i18ndc("kwin_effects", "Name of a KWin Effect", "Fullscreen Animation"), + i18ndc("kwin_effects", "Comment describing the KWin Effect", "Animate transition to/from fullscreen mode"), + QStringLiteral("Appearance"), + QStringLiteral("fullscreen"), + QUrl(), + true, + false, +#ifdef EFFECT_BUILTINS + &createHelper, + &FullScreenEffect::supported, + nullptr +#endif EFFECT_FALLBACK }, { QStringLiteral("glide"), diff --git a/effects/fullscreen/CMakeLists.txt b/effects/fullscreen/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/effects/fullscreen/CMakeLists.txt @@ -0,0 +1,7 @@ +####################################### +# Effect + +# Source files +set( kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources} + fullscreen/fullscreen.cpp + ) diff --git a/effects/fullscreen/fullscreen.h b/effects/fullscreen/fullscreen.h new file mode 100644 --- /dev/null +++ b/effects/fullscreen/fullscreen.h @@ -0,0 +1,81 @@ +/******************************************************************** + 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_FULLSCREEN_H +#define KWIN_FULLSCREEN_H + +// Qt +#include + +// KWin +#include + +#include "timeline.h" + +namespace KWin +{ + +class FullScreenEffect : public Effect +{ + Q_OBJECT + +public: + FullScreenEffect(); + + void reconfigure(ReconfigureFlags) override; + + void prePaintScreen(ScreenPrePaintData& data, int time) override; + void postPaintScreen() override; + + void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) override; + void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) override; + + bool isActive() const override { + return !m_animations.empty(); + } + + int requestedEffectChainPosition() const override { + return 60; + } + + static bool supported() { + return effects->animationsSupported(); + } + +private Q_SLOTS: + void windowFullScreenChanged(EffectWindow* w, bool entered, const QRect &oldGeometry); + void windowDeleted(EffectWindow* w); + +private: + struct AnimData { + Timeline timeline; + QRect startGeo; + QRect endGeo; + }; + +private: + int m_duration; + + QHash m_animations; +}; + +} + +#endif diff --git a/effects/fullscreen/fullscreen.cpp b/effects/fullscreen/fullscreen.cpp new file mode 100644 --- /dev/null +++ b/effects/fullscreen/fullscreen.cpp @@ -0,0 +1,178 @@ +/******************************************************************** + 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 "fullscreen.h" + +#include + +namespace KWin +{ + +FullScreenEffect::FullScreenEffect() +{ + reconfigure(ReconfigureAll); + connect(effects, &EffectsHandler::windowFullScreenChanged, + this, &FullScreenEffect::windowFullScreenChanged); + connect(effects, &EffectsHandler::windowDeleted, + this, &FullScreenEffect::windowDeleted); + +} + +void FullScreenEffect::reconfigure(ReconfigureFlags) +{ + m_duration = animationTime(200); +} + +void FullScreenEffect::prePaintScreen(ScreenPrePaintData& data, int time) +{ + if (! m_animations.empty()) { + auto it = m_animations.begin(); + while (it != m_animations.end()) { + AnimData* anim = &it.value(); + anim->timeline.update(time); + ++it; + } + data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + } + effects->prePaintScreen(data, time); +} + +void FullScreenEffect::postPaintScreen() +{ + if (! m_animations.empty()) { + auto it = m_animations.begin(); + while (it != m_animations.end()) { + AnimData* anim = &it.value(); + if (anim->timeline.done()) { + EffectWindow* w = it.key(); + w->unreferencePreviousWindowPixmap(); + it = m_animations.erase(it); + } else { + ++it; + } + } + effects->addRepaintFull(); + } + effects->postPaintScreen(); +} + +void FullScreenEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) +{ + if (m_animations.contains(w)) { + data.setTransformed(); + } + effects->prePaintWindow(w, data, time); +} + +void FullScreenEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) +{ + if (m_animations.contains(w)) { + AnimData* anim = &m_animations[w]; + + // Funky stuff: Even though direction of the timeline has been changed, + // some extra steps should be taken. Normally, when we want to revert back + // an animation, which is currently in progress, we change direction of + // a timeline associated with this animation. This works fine until some of + // endpoints changes. So, let's take a look at our case: + // + // startGeo endGeo + // (A)---------------------------------------------(B) + // + // @p w's geometry is interpolated between startGeo and endGeo: + // + // (A)(X)------------------------------------------(B) : t = 0 + // (A)--------------------(X)----------------------(B) : t = 0.5 + // (A)------------------------------------------(X)(B) : t = 1 + // + // Let's assume, for example, @p w has entered fullscreen mode. So, + // A is @p w's geometry before it entered fullscreen mode and B is + // @p w's geometry in fullscreen mode. In addition to the assumption + // above, let's say t = 0.1. + // + // (A)---(X)---------------------------------------(B) + // + // ... and @p w left fullscreen mode. Now, @p w's geometry is interpolated + // between B and A. Also, timeline changed its direction(e.g. it goes + // from 0.1 to 0.0): + // + // (B)---(X)---------------------------------------(A) + // + // In other words, @p w's geometry moves toward B, which is totally wrong. + // That's why progress should be reversed if timeline goes backwad. + // + // (B)---------------------------------------(X)---(A) + // + float transformProgress = (anim->timeline.direction() == Timeline::Backward) + ? anim->timeline.rprogress() + : anim->timeline.progress(); + + // Morph rectangle `anim->startGeo` into rectangle `anim->endGeo`. + QVector2D pos(interpolate(anim->startGeo.x(), anim->endGeo.x(), transformProgress), + interpolate(anim->startGeo.y(), anim->endGeo.y(), transformProgress)); + QVector2D shift = pos - QVector2D(anim->endGeo.x(), anim->endGeo.y()); + data += shift; + + Q_ASSERT(anim->endGeo.width() > 0 && anim->endGeo.height() > 0); + QVector2D size(interpolate(anim->startGeo.width(), anim->endGeo.width(), transformProgress), + interpolate(anim->startGeo.height(), anim->endGeo.height(), transformProgress)); + QVector2D scale = size / QVector2D(anim->endGeo.width(), anim->endGeo.height()); + data *= scale; + + // Cross fade between previous and current window pixmap. + float crossFadeProgress = anim->timeline.progress(); + data.setCrossFadeProgress(crossFadeProgress); + } + + effects->paintWindow(w, mask, region, data); +} + +void FullScreenEffect::windowFullScreenChanged(EffectWindow* w, bool entered, const QRect &oldGeometry) +{ + if (effects->activeFullScreenEffect()) { + return; + } + + AnimData* anim = nullptr; + + if (m_animations.contains(w)) { + anim = &m_animations[w]; + anim->timeline.toggleDirection(); + } else { + anim = &m_animations[w]; + anim->timeline.setDuration(m_duration); + anim->timeline.setDirection(Timeline::Forward); + anim->timeline.setEasingCurve(QEasingCurve::OutCubic); + w->referencePreviousWindowPixmap(); + } + + anim->startGeo = oldGeometry; + anim->endGeo = w->geometry(); +} + +void FullScreenEffect::windowDeleted(EffectWindow* w) +{ + if (! m_animations.contains(w)) { + return; + } + w->unreferencePreviousWindowPixmap(); + m_animations.remove(w); +} + +} diff --git a/effects/fullscreen/timeline.h b/effects/fullscreen/timeline.h new file mode 100644 --- /dev/null +++ b/effects/fullscreen/timeline.h @@ -0,0 +1,188 @@ +/******************************************************************** + 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_FULLSCREEN_TIMELINE_H +#define KWIN_FULLSCREEN_TIMELINE_H + +#include + +namespace KWin +{ + +class Timeline +{ +public: + enum Direction { + Forward, + Backward + }; + +public: + explicit Timeline(int duration = 1000, Direction direction = Forward) + : m_duration(duration) + , m_direction(direction) + , m_elapsed(0) + , m_finished(false) {} + virtual ~Timeline() {} + + /** + * TODO: write description. + **/ + float progress() const { + Q_ASSERT(m_duration > 0); + auto t = static_cast(m_elapsed) / m_duration; + if (m_direction == Backward) { + t = 1 - t; + } + return m_easingCurve.valueForProgress(t); + } + + /** + * TODO: write description. + **/ + float rprogress() const { + Q_ASSERT(m_duration > 0); + auto t = static_cast(m_elapsed) / m_duration; + if (m_direction == Forward) { + t = 1 - t; + } + return m_easingCurve.valueForProgress(t); + } + + /** + * TODO: write description. + **/ + void update(int delta) { + Q_ASSERT(delta >= 0); + if (m_finished) { + return; + } + m_elapsed += delta; + if (m_elapsed >= m_duration) { + m_finished = true; + m_elapsed = m_duration; + } + } + + /** + * TODO: write description. + **/ + int elapsed() const { + return m_elapsed; + } + + /** + * TODO: write description. + **/ + void setElapsed(int elapsed) { + reset(); + update(elapsed); + } + + /** + * TODO: write description. + **/ + int duration() const { + return m_duration; + } + + /** + * TODO: write description. + **/ + void setDuration(int duration) { + Q_ASSERT(duration > 0); + m_duration = duration; + } + + /** + * TODO: write description. + **/ + Direction direction() const { + return m_direction; + } + + /** + * TODO: write description. + **/ + void setDirection(Direction direction) { + if (m_direction == direction) { + return; + } + if (m_elapsed > 0) { + m_elapsed = m_duration - m_elapsed; + } + m_direction = direction; + } + + /** + * TODO: write description. + **/ + void toggleDirection() { + setDirection(m_direction == Forward ? Backward : Forward); + } + + /** + * TODO: write description. + **/ + QEasingCurve easingCurve() const { + return m_easingCurve; + } + + /** + * TODO: write description. + **/ + void setEasingCurve(const QEasingCurve &easingCurve) { + m_easingCurve = easingCurve; + } + + /** + * TODO: write description. + **/ + void setEasingCurve(QEasingCurve::Type type) { + m_easingCurve.setType(type); + } + + /** + * TODO: write description. + **/ + bool done() const { + return m_finished; + } + + /** + * TODO: write description. + **/ + void reset() { + m_elapsed = 0; + m_finished = false; + } + +private: + int m_duration; + Direction m_direction; + QEasingCurve m_easingCurve; + + int m_elapsed; + bool m_finished; +}; + +} // namespace + +#endif diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -1480,6 +1480,14 @@ * @since 4.7 **/ void windowUnminimized(KWin::EffectWindow *w); + /** + * Signal emitted when a window either enters or leaves fullscreen mode. + * @param w The window which entered/left fullscreen mode + * @param entered @c true if the window entered fullscreen mode, @c false otherwise + * @param oldGeometry Geometry of the window before it entered/left fullscreen mode + * @since 5.XX + **/ + void windowFullScreenChanged(KWin::EffectWindow *w, bool entered, const QRect &oldGeometry); /** * Signal emitted when a window either becomes modal (ie. blocking for its main client) or looses that state. * @param w The window which was unminimized diff --git a/shell_client.h b/shell_client.h --- a/shell_client.h +++ b/shell_client.h @@ -45,7 +45,7 @@ CloseWindow = 0, FocusWindow }; - + class KWIN_EXPORT ShellClient : public AbstractClient { Q_OBJECT @@ -98,6 +98,9 @@ } bool noBorder() const override; void setFullScreen(bool set, bool user = true) override; + QRect geometryFSRestore() const override { + return m_geomFsRestore; + } void setNoBorder(bool set) override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void setOnAllActivities(bool set) override; @@ -170,6 +173,9 @@ void setGeometryRestore(const QRect &geo) override { m_geomMaximizeRestore = geo; } + void setGeometryFSRestore(const QRect &geo) override { + m_geomFsRestore = geo; + } void doResizeSync() override; bool isWaitingForMoveResizeSync() const override; bool acceptsFocus() const override;