Commit a6b5efd4 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii
Browse files

effects: Improve gesture handling in some effects

At the moment, it's possible to activate a qtquick effect while another
qtquick effect is already active, we have code that prevents fullscreen
effects overriding each other, yet that still happens.

The reason for that behavior is that the gesture recognizer will mark
all gestures as started when user starts swiping on touchpad and cancel
gestures as more swiping occurs. This can mistrigger toggling logic in
the window view effect and the desktop grid effect, etc.

In order to make handling of gesture cancellation correct, we could
check whether user has swiped enough to deactivate window view, or
desktop grid. This change tries to implement exactly that.

As a side-effect, it also allows toggling the effect with the same
gesture. However, we should make cancellation gestures opposite of
activation gestures, i.e. if 4 swipe up gesture is used to activate an
effect, then 4 swipe down gesture should be used to deactivate the
effect.
parent 32e6632d
Pipeline #192190 passed with stage
in 15 minutes and 30 seconds
......@@ -46,33 +46,33 @@ DesktopGridEffect::DesktopGridEffect()
});
connect(m_realtimeToggleAction, &QAction::triggered, this, [this]() {
if (isRunning() && m_partialActivationFactor > 0.5) {
activate();
} else {
deactivate(animationDuration());
if (m_status == Status::Deactivating) {
if (m_partialActivationFactor < 0.5) {
deactivate(animationDuration());
} else {
cancelPartialDeactivate();
}
} else if (m_status == Status::Activating) {
if (m_partialActivationFactor > 0.5) {
activate();
} else {
cancelPartialActivate();
}
}
m_partialActivationFactor = 0;
Q_EMIT gestureInProgressChanged();
Q_EMIT partialActivationFactorChanged();
});
effects->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Up, 4, m_realtimeToggleAction, [this](qreal progress) {
if (m_status == Status::Active) {
return;
}
if (effects->hasActiveFullScreenEffect() && effects->activeFullScreenEffect() != this) {
return;
}
const bool wasInProgress = m_partialActivationFactor > 0;
m_partialActivationFactor = progress;
Q_EMIT partialActivationFactorChanged();
if (!wasInProgress) {
Q_EMIT gestureInProgressChanged();
}
if (!isRunning()) {
partialActivate();
if (!effects->hasActiveFullScreenEffect() || effects->activeFullScreenEffect() == this) {
switch (m_status) {
case Status::Inactive:
case Status::Activating:
partialActivate(progress);
break;
case Status::Active:
case Status::Deactivating:
partialDeactivate(progress);
break;
}
}
});
......@@ -128,22 +128,12 @@ void DesktopGridEffect::reconfigure(ReconfigureFlags)
}
const int rows = gridRows();
const int columns = gridColumns();
const bool wasInProgress = m_partialActivationFactor > 0;
int maxDelta = 500; // Arbitrary logical pixels value seems to behave better than scaledScreenSize
if (border == ElectricTop || border == ElectricBottom) {
maxDelta = (screen->geometry().height() / rows) * (rows - (effects->currentDesktop() % rows));
m_partialActivationFactor = std::min(1.0, qAbs(deltaProgress.height()) / maxDelta);
const int maxDelta = (screen->geometry().height() / rows) * (rows - (effects->currentDesktop() % rows));
partialActivate(std::min(1.0, qAbs(deltaProgress.height()) / maxDelta));
} else {
maxDelta = (screen->geometry().width() / columns) * (columns - (effects->currentDesktop() % columns));
m_partialActivationFactor = std::min(1.0, qAbs(deltaProgress.width()) / maxDelta);
}
Q_EMIT partialActivationFactorChanged();
if (!wasInProgress) {
Q_EMIT gestureInProgressChanged();
}
if (!isRunning()) {
partialActivate();
const int maxDelta = (screen->geometry().width() / columns) * (columns - (effects->currentDesktop() % columns));
partialActivate(std::min(1.0, qAbs(deltaProgress.width()) / maxDelta));
}
});
}
......@@ -288,9 +278,25 @@ qreal DesktopGridEffect::partialActivationFactor() const
return m_partialActivationFactor;
}
void DesktopGridEffect::setPartialActivationFactor(qreal factor)
{
if (m_partialActivationFactor != factor) {
m_partialActivationFactor = factor;
Q_EMIT partialActivationFactorChanged();
}
}
bool DesktopGridEffect::gestureInProgress() const
{
return m_partialActivationFactor > 0;
return m_gestureInProgress;
}
void DesktopGridEffect::setGestureInProgress(bool gesture)
{
if (m_gestureInProgress != gesture) {
m_gestureInProgress = gesture;
Q_EMIT gestureInProgressChanged();
}
}
void DesktopGridEffect::toggle()
......@@ -300,9 +306,6 @@ void DesktopGridEffect::toggle()
} else {
deactivate(animationDuration());
}
m_partialActivationFactor = 0;
Q_EMIT gestureInProgressChanged();
Q_EMIT partialActivationFactorChanged();
}
void DesktopGridEffect::activate()
......@@ -310,19 +313,36 @@ void DesktopGridEffect::activate()
if (effects->isScreenLocked()) {
return;
}
m_status = Status::Active;
setGestureInProgress(false);
setPartialActivationFactor(0.0);
// This one should be the last.
setRunning(true);
}
void DesktopGridEffect::partialActivate()
void DesktopGridEffect::partialActivate(qreal factor)
{
if (effects->isScreenLocked()) {
return;
}
m_status = Status::Activating;
setPartialActivationFactor(factor);
setGestureInProgress(true);
// This one should be the last.
setRunning(true);
}
void DesktopGridEffect::cancelPartialActivate()
{
deactivate(animationDuration());
}
void DesktopGridEffect::deactivate(int timeout)
{
const auto screenViews = views();
......@@ -330,7 +350,22 @@ void DesktopGridEffect::deactivate(int timeout)
QMetaObject::invokeMethod(view->rootItem(), "stop");
}
m_shutdownTimer->start(timeout);
m_status = Status::Inactive;
setGestureInProgress(false);
setPartialActivationFactor(0.0);
}
void DesktopGridEffect::partialDeactivate(qreal factor)
{
m_status = Status::Deactivating;
setPartialActivationFactor(1 - factor);
setGestureInProgress(true);
}
void DesktopGridEffect::cancelPartialDeactivate()
{
activate();
}
void DesktopGridEffect::realDeactivate()
......
......@@ -36,6 +36,7 @@ public:
enum class Status {
Inactive,
Activating,
Deactivating,
Active
};
......@@ -51,7 +52,10 @@ public:
bool showAddRemove() const;
qreal partialActivationFactor() const;
void setPartialActivationFactor(qreal factor);
bool gestureInProgress() const;
void setGestureInProgress(bool gesture);
int gridRows() const;
int gridColumns() const;
......@@ -71,7 +75,10 @@ public:
public Q_SLOTS:
void activate();
void partialActivate();
void partialActivate(qreal factor);
void cancelPartialActivate();
void partialDeactivate(qreal factor);
void cancelPartialDeactivate();
void deactivate(int timeout);
void toggle();
......@@ -103,6 +110,7 @@ private:
Status m_status = Status::Inactive;
int m_animationDuration = 200;
int m_layout = 1;
bool m_gestureInProgress = false;
};
} // namespace KWin
......@@ -24,18 +24,6 @@ OverviewEffect::OverviewEffect()
m_shutdownTimer->setSingleShot(true);
connect(m_shutdownTimer, &QTimer::timeout, this, &OverviewEffect::realDeactivate);
m_realtimeToggleAction = new QAction(this);
connect(m_realtimeToggleAction, &QAction::triggered, this, [this]() {
if (isRunning() && m_partialActivationFactor > 0.5) {
activate();
} else {
deactivate();
}
m_partialActivationFactor = 0;
Q_EMIT gestureInProgressChanged();
Q_EMIT partialActivationFactorChanged();
});
const QKeySequence defaultToggleShortcut = Qt::META | Qt::Key_W;
m_toggleAction = new QAction(this);
connect(m_toggleAction, &QAction::triggered, this, &OverviewEffect::toggle);
......@@ -46,18 +34,35 @@ OverviewEffect::OverviewEffect()
m_toggleShortcut = KGlobalAccel::self()->shortcut(m_toggleAction);
effects->registerGlobalShortcut({defaultToggleShortcut}, m_toggleAction);
auto progressCallback = [this](qreal progress) {
if (m_status == Status::Active) {
return;
}
const bool wasInProgress = m_partialActivationFactor > 0;
m_partialActivationFactor = progress;
Q_EMIT partialActivationFactorChanged();
if (!wasInProgress) {
Q_EMIT gestureInProgressChanged();
m_realtimeToggleAction = new QAction(this);
connect(m_realtimeToggleAction, &QAction::triggered, this, [this]() {
if (m_status == Status::Deactivating) {
if (m_partialActivationFactor < 0.5) {
deactivate();
} else {
cancelPartialDeactivate();
}
} else if (m_status == Status::Activating) {
if (m_partialActivationFactor > 0.5) {
activate();
} else {
cancelPartialActivate();
}
}
if (!isRunning()) {
partialActivate();
});
auto progressCallback = [this](qreal progress) {
if (!effects->hasActiveFullScreenEffect() || effects->activeFullScreenEffect() == this) {
switch (m_status) {
case Status::Inactive:
case Status::Activating:
partialActivate(progress);
break;
case Status::Active:
case Status::Deactivating:
partialDeactivate(progress);
break;
}
}
};
......@@ -116,19 +121,11 @@ void OverviewEffect::reconfigure(ReconfigureFlags)
if (m_status == Status::Active) {
return;
}
const bool wasInProgress = m_partialActivationFactor > 0;
const int maxDelta = 500; // Arbitrary logical pixels value seems to behave better than scaledScreenSize
if (border == ElectricTop || border == ElectricBottom) {
m_partialActivationFactor = std::min(1.0, qAbs(deltaProgress.height()) / maxDelta);
partialActivate(std::min(1.0, qAbs(deltaProgress.height()) / maxDelta));
} else {
m_partialActivationFactor = std::min(1.0, qAbs(deltaProgress.width()) / maxDelta);
}
Q_EMIT partialActivationFactorChanged();
if (!wasInProgress) {
Q_EMIT gestureInProgressChanged();
}
if (!isRunning()) {
partialActivate();
partialActivate(std::min(1.0, qAbs(deltaProgress.width()) / maxDelta));
}
});
}
......@@ -183,9 +180,25 @@ qreal OverviewEffect::partialActivationFactor() const
return m_partialActivationFactor;
}
void OverviewEffect::setPartialActivationFactor(qreal factor)
{
if (m_partialActivationFactor != factor) {
m_partialActivationFactor = factor;
Q_EMIT partialActivationFactorChanged();
}
}
bool OverviewEffect::gestureInProgress() const
{
return m_partialActivationFactor > 0;
return m_gestureInProgress;
}
void OverviewEffect::setGestureInProgress(bool gesture)
{
if (m_gestureInProgress != gesture) {
m_gestureInProgress = gesture;
Q_EMIT gestureInProgressChanged();
}
}
int OverviewEffect::requestedEffectChainPosition() const
......@@ -209,9 +222,6 @@ void OverviewEffect::toggle()
} else {
deactivate();
}
m_partialActivationFactor = 0;
Q_EMIT gestureInProgressChanged();
Q_EMIT partialActivationFactorChanged();
}
void OverviewEffect::activate()
......@@ -219,19 +229,36 @@ void OverviewEffect::activate()
if (effects->isScreenLocked()) {
return;
}
m_status = Status::Active;
setGestureInProgress(false);
setPartialActivationFactor(0.0);
// This one should be the last.
setRunning(true);
}
void OverviewEffect::partialActivate()
void OverviewEffect::partialActivate(qreal factor)
{
if (effects->isScreenLocked()) {
return;
}
m_status = Status::Activating;
setPartialActivationFactor(factor);
setGestureInProgress(true);
// This one should be the last.
setRunning(true);
}
void OverviewEffect::cancelPartialActivate()
{
deactivate();
}
void OverviewEffect::deactivate()
{
const auto screenViews = views();
......@@ -239,7 +266,22 @@ void OverviewEffect::deactivate()
QMetaObject::invokeMethod(view->rootItem(), "stop");
}
m_shutdownTimer->start(animationDuration());
m_status = Status::Inactive;
setGestureInProgress(false);
setPartialActivationFactor(0.0);
}
void OverviewEffect::partialDeactivate(qreal factor)
{
m_status = Status::Deactivating;
setPartialActivationFactor(1.0 - factor);
setGestureInProgress(true);
}
void OverviewEffect::cancelPartialDeactivate()
{
activate();
}
void OverviewEffect::realDeactivate()
......
......@@ -26,6 +26,7 @@ public:
enum class Status {
Inactive,
Activating,
Deactivating,
Active
};
OverviewEffect();
......@@ -43,7 +44,10 @@ public:
void setBlurBackground(bool blur);
qreal partialActivationFactor() const;
void setPartialActivationFactor(qreal factor);
bool gestureInProgress() const;
void setGestureInProgress(bool gesture);
int requestedEffectChainPosition() const override;
bool borderActivated(ElectricBorder border) override;
......@@ -60,7 +64,10 @@ Q_SIGNALS:
public Q_SLOTS:
void activate();
void partialActivate();
void partialActivate(qreal factor);
void cancelPartialActivate();
void partialDeactivate(qreal factor);
void cancelPartialDeactivate();
void deactivate();
void quickDeactivate();
void toggle();
......@@ -82,6 +89,7 @@ private:
Status m_status = Status::Inactive;
int m_animationDuration = 200;
int m_layout = 1;
bool m_gestureInProgress = false;
};
} // namespace KWin
......@@ -40,18 +40,6 @@ WindowViewEffect::WindowViewEffect()
setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/windowview/qml/main.qml"))));
m_realtimeToggleAction = new QAction(this);
connect(m_realtimeToggleAction, &QAction::triggered, this, [this]() {
if (isRunning() && m_partialActivationFactor > 0.5) {
activate();
} else {
deactivate(animationDuration());
}
m_partialActivationFactor = 0;
Q_EMIT gestureInProgressChanged();
Q_EMIT partialActivationFactorChanged();
});
m_exposeAction->setObjectName(QStringLiteral("Expose"));
m_exposeAction->setText(i18n("Toggle Present Windows (Current desktop)"));
KGlobalAccel::self()->setDefaultShortcut(m_exposeAction, QList<QKeySequence>() << (Qt::CTRL | Qt::Key_F9));
......@@ -93,18 +81,35 @@ WindowViewEffect::WindowViewEffect()
}
});
const auto gestureCallback = [this](qreal progress) {
if (m_status == Status::Active) {
return;
}
const bool wasInProgress = m_partialActivationFactor > 0;
m_partialActivationFactor = progress;
Q_EMIT partialActivationFactorChanged();
if (!wasInProgress) {
Q_EMIT gestureInProgressChanged();
m_realtimeToggleAction = new QAction(this);
connect(m_realtimeToggleAction, &QAction::triggered, this, [this]() {
if (m_status == Status::Deactivating) {
if (m_partialActivationFactor < 0.5) {
deactivate(animationDuration());
} else {
cancelPartialDeactivate();
}
} else if (m_status == Status::Activating) {
if (m_partialActivationFactor > 0.5) {
activate();
} else {
cancelPartialActivate();
}
}
if (!isRunning()) {
partialActivate();
});
const auto gestureCallback = [this](qreal progress) {
if (!effects->hasActiveFullScreenEffect() || effects->activeFullScreenEffect() == this) {
switch (m_status) {
case Status::Inactive:
case Status::Activating:
partialActivate(progress);
break;
case Status::Active:
case Status::Deactivating:
partialDeactivate(progress);
break;
}
}
};
effects->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Down, 4, m_realtimeToggleAction, gestureCallback);
......@@ -209,19 +214,11 @@ void WindowViewEffect::reconfigure(ReconfigureFlags)
} else if (m_touchBorderActivateClass.contains(border)) {
setMode(ModeWindowClass);
}
const bool wasInProgress = m_partialActivationFactor > 0;
const int maxDelta = 500; // Arbitrary logical pixels value seems to behave better than scaledScreenSize
if (border == ElectricTop || border == ElectricBottom) {
m_partialActivationFactor = std::min(1.0, qAbs(deltaProgress.height()) / maxDelta);
partialActivate(std::min(1.0, qAbs(deltaProgress.height()) / maxDelta));
} else {
m_partialActivationFactor = std::min(1.0, qAbs(deltaProgress.width()) / maxDelta);
}
Q_EMIT partialActivationFactorChanged();
if (!wasInProgress) {
Q_EMIT gestureInProgressChanged();
}
if (!isRunning()) {
partialActivate();
partialActivate(std::min(1.0, qAbs(deltaProgress.width()) / maxDelta));
}
};
......@@ -268,9 +265,25 @@ qreal WindowViewEffect::partialActivationFactor() const
return m_partialActivationFactor;
}
void WindowViewEffect::setPartialActivationFactor(qreal factor)
{
if (m_partialActivationFactor != factor) {
m_partialActivationFactor = factor;
Q_EMIT partialActivationFactorChanged();
}
}
bool WindowViewEffect::gestureInProgress() const
{
return m_partialActivationFactor > 0;
return m_gestureInProgress;
}
void WindowViewEffect::setGestureInProgress(bool gesture)
{
if (m_gestureInProgress != gesture) {
m_gestureInProgress = gesture;
Q_EMIT gestureInProgressChanged();
}
}
void WindowViewEffect::activate(const QStringList &windowIds)
......@@ -303,20 +316,37 @@ void WindowViewEffect::activate()
if (effects->isScreenLocked()) {
return;
}
m_windowIds.clear();
m_status = Status::Active;
m_windowIds.clear();
setGestureInProgress(false);
setPartialActivationFactor(0);
// This one should be the last.
setRunning(true);
}
void WindowViewEffect::partialActivate()
void WindowViewEffect::partialActivate(qreal factor)
{
if (effects->isScreenLocked()) {
return;
}
m_status = Status::Activating;
setPartialActivationFactor(factor);
setGestureInProgress(true);
// This one should be the last.
setRunning(true);
}
void WindowViewEffect::cancelPartialActivate()
{
deactivate(animationDuration());
}
void WindowViewEffect::deactivate(int timeout)
{
const auto screenViews = views();
......@@ -324,7 +354,22 @@ void WindowViewEffect::deactivate(int timeout)
QMetaObject::invokeMethod(view->rootItem(), "stop");
}
m_shutdownTimer->start(timeout);
m_status = Status::Inactive;
setGestureInProgress(false);
setPartialActivationFactor(0.0);
}
void WindowViewEffect::partialDeactivate(qreal factor)
{
m_status = Status::Deactivating;
setPartialActivationFactor(1.0 - factor);
setGestureInProgress(true);
}