Commit 4cb3ab09 authored by Marco Martin's avatar Marco Martin
Browse files

Realtime screen edges gestures for scripted effects

Possibility to implement realtime screenedges gestures in scripted effects,
implement it in the windowsaperture show desktop effect.
* Expose registerRealtimeScreenEdge to JavaScript, the callback will be
a JS function.
* Add the concept of freezeInTime() in the animation js bindings,
it will either create an animation frozen at a given time or freeze a running animation
that can be restored and ran to completition at any time
* add an edges property only for showdesktop as it's not directly on the effect configuration
parent 46bbe4ff
Pipeline #171411 passed with stage
in 24 minutes and 54 seconds
......@@ -68,7 +68,7 @@ void DontCrashCancelAnimationFromAnimationEndedTest::cleanup()
void DontCrashCancelAnimationFromAnimationEndedTest::testScript()
{
// load a scripted effect which deletes animation data
ScriptedEffect *effect = ScriptedEffect::create(QStringLiteral("crashy"), QFINDTESTDATA("data/anim-data-delete-effect/effect.js"), 10);
ScriptedEffect *effect = ScriptedEffect::create(QStringLiteral("crashy"), QFINDTESTDATA("data/anim-data-delete-effect/effect.js"), 10, QString());
QVERIFY(effect);
const auto children = effects->children();
......
......@@ -12,10 +12,17 @@
var badBadWindowsEffect = {
duration: animationTime(250),
showingDesktop: false,
loadConfig: function () {
badBadWindowsEffect.duration = animationTime(250);
},
offToCorners: function (showing) {
setShowingDesktop: function (showing) {
badBadWindowsEffect.showingDesktop = showing;
},
offToCorners: function (showing, frozenTime) {
if (typeof frozenTime === "undefined") {
frozenTime = -1;
}
var stackingOrder = effects.stackingOrder;
var screenGeo = effects.virtualScreenGeometry;
var xOffset = screenGeo.width / 16;
......@@ -28,23 +35,19 @@ var badBadWindowsEffect = {
// ignore windows above the desktop
// (when not showing, pretty much everything would be)
if (w.desktopWindow)
break;
if (w.desktopWindow) {
if (frozenTime <= 0)
break
else
continue;
}
// ignore invisible windows and such that do not have to be restored
if (!w.visible)
continue;
// we just fade out docks - moving panels into edges looks dull
// Don't touch docks
if (w.dock) {
w.offToCornerId = set({
window: w,
duration: badBadWindowsEffect.duration,
animations: [{
type: Effect.Opacity,
to: 0.0
}]
});
continue;
}
......@@ -115,18 +118,7 @@ var badBadWindowsEffect = {
// keep windows above the desktop visually
effects.setElevatedWindow(w, showing);
if (!showing && w.dock) {
cancel(w.offToCornerId);
delete w.offToCornerId;
delete w.apertureCorner; // should not exist, but better safe than sorry.
animate({
window: w,
duration: badBadWindowsEffect.duration,
animations: [{
type: Effect.Opacity,
from: 0.0
}]
});
if (w.dock) {
continue;
}
......@@ -148,44 +140,85 @@ var badBadWindowsEffect = {
}
if (showing) {
w.offToCornerId = set({
window: w,
duration: badBadWindowsEffect.duration,
curve: QEasingCurve.InOutCubic,
animations: [{
type: Effect.Position,
targetAnchor: anchor,
to: { value1: tx, value2: ty }
},{
type: Effect.Opacity,
to: 0.0
}]
});
} else {
cancel(w.offToCornerId);
delete w.offToCornerId;
delete w.apertureCorner;
if (w.visible) { // could meanwhile have been hidden
animate({
if (!w.offToCornerId || !freezeInTime(w.offToCornerId, frozenTime)) {
w.offToCornerId = set({
window: w,
duration: badBadWindowsEffect.duration,
curve: QEasingCurve.InOutCubic,
animations: [{
type: Effect.Position,
sourceAnchor: anchor,
from: { value1: tx, value2: ty }
targetAnchor: anchor,
to: { value1: tx, value2: ty },
frozenTime: frozenTime
},{
type: Effect.Opacity,
from: 0.0
to: 0.0,
frozenTime: frozenTime
}]
});
}
} else {
if (!w.offToCornerId || !redirect(w.offToCornerId, Effect.Backward) || !freezeInTime(w.offToCornerId, frozenTime)) {
cancel(w.offToCornerId);
delete w.offToCornerId;
delete w.apertureCorner;
if (w.visible) { // could meanwhile have been hidden
animate({
window: w,
duration: badBadWindowsEffect.duration,
curve: QEasingCurve.InOutCubic,
animations: [{
type: Effect.Position,
sourceAnchor: anchor,
gesture: true,
from: { value1: tx, value2: ty }
},{
type: Effect.Opacity,
from: 0.0
}]
});
}
}
}
}
},
realtimeScreenEdgeCallback: function (border, deltaProgress, effectScreen) {
if (!deltaProgress || !effectScreen) {
badBadWindowsEffect.offToCorners(badBadWindowsEffect.showingDesktop, -1);
return;
}
let time = 0;
switch (border) {
case KWin.ElectricTop:
case KWin.ElectricBottom:
time = Math.min(1, (Math.abs(deltaProgress.height) / (effectScreen.geometry.height / 2))) * badBadWindowsEffect.duration;
break;
case KWin.ElectricLeft:
case KWin.ElectricRight:
time = Math.min(1, (Math.abs(deltaProgress.width) / (effectScreen.geometry.width / 2))) * badBadWindowsEffect.duration;
break;
default:
return;
}
if (badBadWindowsEffect.showingDesktop) {
time = badBadWindowsEffect.duration - time;
}
badBadWindowsEffect.offToCorners(true, time)
},
init: function () {
badBadWindowsEffect.loadConfig();
effects.showingDesktopChanged.connect(badBadWindowsEffect.setShowingDesktop);
effects.showingDesktopChanged.connect(badBadWindowsEffect.offToCorners);
let edges = effect.touchEdgesForAction("show-desktop");
for (let i in edges) {
let edge = parseInt(edges[i]);
registerRealtimeScreenEdge(edge, badBadWindowsEffect.realtimeScreenEdgeCallback);
}
}
};
......
......@@ -62,6 +62,7 @@ AniData::AniData()
, customCurve(0) // Linear
, meta(0)
, startTime(0)
, frozenTime(-1)
, waitAtSource(false)
, keepAlive(true)
, lastPresentTime(std::chrono::milliseconds::zero())
......@@ -77,6 +78,7 @@ AniData::AniData(AnimationEffect::Attribute a, int meta_, const FPx2 &to_,
, to(to_)
, meta(meta_)
, startTime(AnimationEffect::clock() + delay)
, frozenTime(-1)
, fullScreenEffectLock(std::move(fullScreenEffectLock_))
, waitAtSource(waitAtSource_)
, keepAlive(keepAlive)
......
......@@ -85,6 +85,7 @@ public:
FPx2 from, to;
TimeLine timeLine;
uint meta;
qint64 frozenTime;
qint64 startTime;
QSharedPointer<FullScreenEffectLock> fullScreenEffectLock;
bool waitAtSource;
......
......@@ -11,6 +11,7 @@
#include "kwinanimationeffect.h"
#include "anidata_p.h"
#include <QAction>
#include <QDateTime>
#include <QTimer>
#include <QVector3D>
......@@ -310,6 +311,31 @@ bool AnimationEffect::retarget(quint64 animationId, FPx2 newTarget, int newRemai
return false; // no animation found
}
bool AnimationEffect::freezeInTime(quint64 animationId, qint64 frozenTime)
{
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) {
if (anim->id == animationId) {
if (frozenTime >= 0) {
anim->timeLine.setElapsed(std::chrono::milliseconds(frozenTime));
}
anim->frozenTime = frozenTime;
return true;
}
}
}
return false; // no animation found
}
bool AnimationEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags)
{
Q_D(AnimationEffect);
......@@ -405,7 +431,7 @@ void AnimationEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::mill
for (auto entry = d->m_animations.begin(); entry != d->m_animations.end(); ++entry) {
for (auto anim = entry->first.begin(); anim != entry->first.end(); ++anim) {
if (anim->startTime <= clock()) {
if (anim->lastPresentTime.count()) {
if (anim->lastPresentTime.count() && anim->frozenTime < 0) {
anim->timeLine.update(presentTime - anim->lastPresentTime);
}
anim->lastPresentTime = presentTime;
......
......@@ -415,6 +415,8 @@ protected:
*/
bool retarget(quint64 animationId, FPx2 newTarget, int newRemainingTime = -1);
bool freezeInTime(quint64 animationId, qint64 frozenTime);
/**
* Changes the direction of the animation.
*
......
......@@ -1260,6 +1260,11 @@ ElectricBorderAction ScreenEdges::actionForTouchEdge(Edge *edge) const
return ElectricActionNone;
}
ElectricBorderAction ScreenEdges::actionForTouchBorder(ElectricBorder border) const
{
return m_touchCallbacks.value(border);
}
void ScreenEdges::reserveDesktopSwitching(bool isToReserve, Qt::Orientations o)
{
if (!o) {
......
......@@ -358,6 +358,8 @@ public:
ElectricBorderAction actionBottomLeft() const;
ElectricBorderAction actionLeft() const;
ElectricBorderAction actionForTouchBorder(ElectricBorder border) const;
GestureRecognizer *gestureRecognizer() const
{
return m_gestureRecognizer;
......
......@@ -40,13 +40,15 @@ struct AnimationSettings
Delay = 1 << 2,
Duration = 1 << 3,
FullScreen = 1 << 4,
KeepAlive = 1 << 5
KeepAlive = 1 << 5,
FrozenTime = 1 << 6
};
AnimationEffect::Attribute type;
QEasingCurve::Type curve;
QJSValue from;
QJSValue to;
int delay;
qint64 frozenTime;
uint duration;
uint set;
uint metaData;
......@@ -111,6 +113,14 @@ AnimationSettings animationSettingsFromObject(const QJSValue &object)
settings.keepAlive = true;
}
const QJSValue frozenTime = object.property(QStringLiteral("frozenTime"));
if (frozenTime.isNumber()) {
settings.frozenTime = frozenTime.toInt();
settings.set |= AnimationSettings::FrozenTime;
} else {
settings.frozenTime = -1;
}
return settings;
}
......@@ -148,17 +158,20 @@ ScriptedEffect *ScriptedEffect::create(const KPluginMetaData &effect)
qCDebug(KWIN_SCRIPTING) << "Could not locate the effect script";
return nullptr;
}
return ScriptedEffect::create(name, scriptFile, effect.value(QStringLiteral("X-KDE-Ordering")).toInt());
return ScriptedEffect::create(name, scriptFile, effect.value(QStringLiteral("X-KDE-Ordering")).toInt(), effect.value(QStringLiteral("X-KWin-Exclusive-Category")));
}
ScriptedEffect *ScriptedEffect::create(const QString &effectName, const QString &pathToScript, int chainPosition)
ScriptedEffect *ScriptedEffect::create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory)
{
ScriptedEffect *effect = new ScriptedEffect();
effect->m_exclusiveCategory = exclusiveCategory;
if (!effect->init(effectName, pathToScript)) {
delete effect;
return nullptr;
}
effect->m_chainPosition = chainPosition;
return effect;
}
......@@ -248,6 +261,7 @@ bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript
QStringLiteral("registerShortcut"),
QStringLiteral("registerScreenEdge"),
QStringLiteral("registerRealtimeScreenEdge"),
QStringLiteral("registerTouchScreenEdge"),
QStringLiteral("unregisterScreenEdge"),
QStringLiteral("unregisterTouchScreenEdge"),
......@@ -255,6 +269,7 @@ bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript
QStringLiteral("animate"),
QStringLiteral("set"),
QStringLiteral("retarget"),
QStringLiteral("freezeInTime"),
QStringLiteral("redirect"),
QStringLiteral("complete"),
QStringLiteral("cancel"),
......@@ -292,6 +307,26 @@ bool ScriptedEffect::isActiveFullScreenEffect() const
return effects->activeFullScreenEffect() == this;
}
QList<int> ScriptedEffect::touchEdgesForAction(const QString &action) const
{
QList<int> ret;
if (m_exclusiveCategory == QStringLiteral("show-desktop") && action == QStringLiteral("show-desktop")) {
const QVector borders({ElectricTop, ElectricRight, ElectricBottom, ElectricLeft});
for (const auto b : borders) {
if (ScreenEdges::self()->actionForTouchBorder(b) == ElectricActionShowDesktop) {
ret.append(b);
}
}
return ret;
} else {
if (!m_config) {
return ret;
}
return m_config->property(QStringLiteral("TouchBorderActivate") + action).value<QList<int>>();
}
}
QJSValue ScriptedEffect::animate_helper(const QJSValue &object, AnimationType animationType)
{
QJSValue windowProperty = object.property(QStringLiteral("window"));
......@@ -405,6 +440,9 @@ QJSValue ScriptedEffect::animate_helper(const QJSValue &object, AnimationType an
setting.delay,
setting.fullScreenEffect,
setting.keepAlive);
if (setting.frozenTime >= 0) {
freezeInTime(animationId, setting.frozenTime);
}
} else {
animationId = animate(window,
setting.type,
......@@ -416,6 +454,9 @@ QJSValue ScriptedEffect::animate_helper(const QJSValue &object, AnimationType an
setting.delay,
setting.fullScreenEffect,
setting.keepAlive);
if (setting.frozenTime >= 0) {
freezeInTime(animationId, setting.frozenTime);
}
}
array.setProperty(i, animationId);
}
......@@ -473,6 +514,18 @@ bool ScriptedEffect::retarget(const QList<quint64> &animationIds, const QJSValue
});
}
bool ScriptedEffect::freezeInTime(quint64 animationId, qint64 frozenTime)
{
return AnimationEffect::freezeInTime(animationId, frozenTime);
}
bool ScriptedEffect::freezeInTime(const QList<quint64> &animationIds, qint64 frozenTime)
{
return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
return AnimationEffect::freezeInTime(animationId, frozenTime);
});
}
bool ScriptedEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags)
{
return AnimationEffect::redirect(animationId, direction, terminationFlags);
......@@ -635,6 +688,43 @@ bool ScriptedEffect::registerScreenEdge(int edge, const QJSValue &callback)
return true;
}
bool ScriptedEffect::registerRealtimeScreenEdge(int edge, const QJSValue &callback)
{
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("Screen edge handler must be callable"));
return false;
}
auto it = realtimeScreenEdgeCallbacks().find(edge);
if (it == realtimeScreenEdgeCallbacks().end()) {
// not yet registered
realtimeScreenEdgeCallbacks().insert(edge, QJSValueList{callback});
auto *triggerAction = new QAction(this);
connect(triggerAction, &QAction::triggered, this, [this, edge]() {
auto it = realtimeScreenEdgeCallbacks().constFind(edge);
if (it != realtimeScreenEdgeCallbacks().constEnd()) {
for (const QJSValue &callback : it.value()) {
QJSValue(callback).call({edge});
}
}
});
effects->registerRealtimeTouchBorder(static_cast<KWin::ElectricBorder>(edge), triggerAction, [this](ElectricBorder border, const QSizeF &deltaProgress, EffectScreen *screen) {
auto it = realtimeScreenEdgeCallbacks().constFind(border);
if (it != realtimeScreenEdgeCallbacks().constEnd()) {
for (const QJSValue &callback : it.value()) {
QJSValue delta = m_engine->newObject();
delta.setProperty("width", deltaProgress.width());
delta.setProperty("height", deltaProgress.height());
QJSValue(callback).call({border, QJSValue(delta), m_engine->newQObject(screen)});
}
}
});
} else {
it->append(callback);
}
return true;
}
bool ScriptedEffect::unregisterScreenEdge(int edge)
{
auto it = screenEdgeCallbacks().find(edge);
......
......@@ -21,6 +21,7 @@ class KPluginMetaData;
namespace KWin
{
class KWIN_EXPORT ScriptedEffect : public KWin::AnimationEffect
{
Q_OBJECT
......@@ -30,6 +31,7 @@ class KWIN_EXPORT ScriptedEffect : public KWin::AnimationEffect
Q_ENUMS(MetaType)
Q_ENUMS(EasingCurve)
Q_ENUMS(SessionState)
Q_ENUMS(ElectricBorder)
/**
* The plugin ID of the effect
*/
......@@ -38,6 +40,7 @@ class KWIN_EXPORT ScriptedEffect : public KWin::AnimationEffect
* True if we are the active fullscreen effect
*/
Q_PROPERTY(bool isActiveFullScreenEffect READ isActiveFullScreenEffect NOTIFY isActiveFullScreenEffectChanged)
public:
// copied from kwineffects.h
enum DataRole {
......@@ -67,7 +70,7 @@ public:
}
QString activeConfig() const;
void setActiveConfig(const QString &name);
static ScriptedEffect *create(const QString &effectName, const QString &pathToScript, int chainPosition);
static ScriptedEffect *create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory);
static ScriptedEffect *create(const KPluginMetaData &effect);
static bool supported();
~ScriptedEffect() override;
......@@ -116,6 +119,7 @@ public:
Q_SCRIPTABLE void registerShortcut(const QString &objectName, const QString &text,
const QString &keySequence, const QJSValue &callback);
Q_SCRIPTABLE bool registerScreenEdge(int edge, const QJSValue &callback);
Q_SCRIPTABLE bool registerRealtimeScreenEdge(int edge, const QJSValue &callback);
Q_SCRIPTABLE bool unregisterScreenEdge(int edge);
Q_SCRIPTABLE bool registerTouchScreenEdge(int edge, const QJSValue &callback);
Q_SCRIPTABLE bool unregisterTouchScreenEdge(int edge);
......@@ -136,6 +140,8 @@ public:
int newRemainingTime = -1);
Q_SCRIPTABLE bool retarget(const QList<quint64> &animationIds, const QJSValue &newTarget,
int newRemainingTime = -1);
Q_SCRIPTABLE bool freezeInTime(quint64 animationId, qint64 frozenTime);
Q_SCRIPTABLE bool freezeInTime(const QList<quint64> &animationIds, qint64 frozenTime);
Q_SCRIPTABLE bool redirect(quint64 animationId, Direction direction,
TerminationFlags terminationFlags = TerminateAtSource);
......@@ -148,11 +154,18 @@ public:
Q_SCRIPTABLE bool cancel(quint64 animationId);
Q_SCRIPTABLE bool cancel(const QList<quint64> &animationIds);
Q_SCRIPTABLE QList<int> touchEdgesForAction(const QString &action) const;
QHash<int, QJSValueList> &screenEdgeCallbacks()
{
return m_screenEdgeCallbacks;
}
QHash<int, QJSValueList> &realtimeScreenEdgeCallbacks()
{
return m_realtimeScreenEdgeCallbacks;
}
QString pluginId() const;
bool isActiveFullScreenEffect() const;
......@@ -184,13 +197,14 @@ private:
QJSEngine *m_engine;
QString m_effectName;
QString m_scriptFile;
QString m_exclusiveCategory;
QHash<int, QJSValueList> m_screenEdgeCallbacks;
QHash<int, QJSValueList> m_realtimeScreenEdgeCallbacks;
KConfigLoader *m_config;
int m_chainPosition;
QHash<int, QAction *> m_touchScreenEdgeCallbacks;
Effect *m_activeFullScreenEffect = nullptr;
};
}
#endif // KWIN_SCRIPTEDEFFECT_H
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment