Commit 2b6f8a31 authored by Marco Martin's avatar Marco Martin Committed by Bhushan Shah
Browse files

support realtime activation for screen edges gestures

support realtime activation for screenedges gestures, making it possible
for effects to show half-triggered states while dragging from
the edge with a finger, making them much more usable
parent 563e3724
Pipeline #153534 passed with stage
in 13 minutes and 53 seconds
......@@ -1391,6 +1391,13 @@ void EffectsHandlerImpl::registerTouchBorder(ElectricBorder border, QAction *act
ScreenEdges::self()->reserveTouch(border, action);
}
void EffectsHandlerImpl::registerRealtimeTouchBorder(ElectricBorder border, QAction *action, EffectsHandler::TouchBorderCallback progressCallback)
{
ScreenEdges::self()->reserveTouch(border, action, [progressCallback] (ElectricBorder border, const QSizeF &deltaProgress, AbstractOutput *output) {
progressCallback(border, deltaProgress, EffectScreenImpl::get(output));
});
}
void EffectsHandlerImpl::unregisterTouchBorder(ElectricBorder border, QAction *action)
{
ScreenEdges::self()->unreserveTouch(border, action);
......
......@@ -162,6 +162,7 @@ public:
void unreserveElectricBorder(ElectricBorder border, Effect *effect) override;
void registerTouchBorder(ElectricBorder border, QAction *action) override;
void registerRealtimeTouchBorder(ElectricBorder border, QAction *action, EffectsHandler::TouchBorderCallback progressCallback) override;
void unregisterTouchBorder(ElectricBorder border, QAction *action) override;
QPainter* scenePainter() override;
......
......@@ -192,7 +192,26 @@ void DesktopGridEffect::reconfigure(ReconfigureFlags)
if (!relevantBorders.contains(ElectricBorder(i))) {
continue;
}
effects->registerTouchBorder(ElectricBorder(i), m_shortcutAction);
effects->registerRealtimeTouchBorder(ElectricBorder(i), m_gestureAction, [this](ElectricBorder border, const QSizeF &deltaProgress, const EffectScreen *screen) {
if (activated) return;
if (timeline.currentValue() == 0) {
activated = true;
setup();
activated = false;
}
qreal progress = 0;
if (border == ElectricTop || border == ElectricBottom) {
progress = qAbs(deltaProgress.height() / (screen->geometry().height()/2));
} else {
progress = qAbs(deltaProgress.width() / (screen->geometry().width()/2));
}
timeline.setDirection(QTimeLine::Forward);
timeline.setCurrentTime(timeline.duration() * progress);
effects->addRepaintFull();
});
}
}
......
......@@ -88,7 +88,26 @@ void OverviewEffect::reconfigure(ReconfigureFlags)
const QList<int> touchActivateBorders = OverviewConfig::touchBorderActivate();
for (const int &border : touchActivateBorders) {
m_touchBorderActivate.append(ElectricBorder(border));
effects->registerTouchBorder(ElectricBorder(border), m_toggleAction);
effects->registerRealtimeTouchBorder(ElectricBorder(border), m_toggleAction, [this] (ElectricBorder border, const QSizeF &deltaProgress, const EffectScreen *screen) {
Q_UNUSED(screen)
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);
} else {
m_partialActivationFactor = std::min(1.0, qAbs(deltaProgress.width()) / maxDelta);
}
Q_EMIT partialActivationFactorChanged();
if ( !wasInProgress) {
Q_EMIT gestureInProgressChanged();
}
if (!isRunning()) {
partialActivate();
}
});
}
}
......@@ -131,6 +150,16 @@ void OverviewEffect::setBlurBackground(bool blur)
}
}
qreal OverviewEffect::partialActivationFactor() const
{
return m_partialActivationFactor;
}
bool OverviewEffect::gestureInProgress() const
{
return m_partialActivationFactor > 0;
}
int OverviewEffect::requestedEffectChainPosition() const
{
return 70;
......@@ -147,11 +176,14 @@ bool OverviewEffect::borderActivated(ElectricBorder border)
void OverviewEffect::toggle()
{
if (!isRunning()) {
if (!isRunning() || m_partialActivationFactor > 0.5) {
activate();
} else {
deactivate();
}
m_partialActivationFactor = 0;
Q_EMIT gestureInProgressChanged();
Q_EMIT partialActivationFactorChanged();
}
void OverviewEffect::activate()
......@@ -159,6 +191,16 @@ void OverviewEffect::activate()
if (effects->isScreenLocked()) {
return;
}
m_status = Status::Active;
setRunning(true);
}
void OverviewEffect::partialActivate()
{
if (effects->isScreenLocked()) {
return;
}
m_status = Status::Activating;
setRunning(true);
}
......@@ -169,11 +211,13 @@ void OverviewEffect::deactivate()
QMetaObject::invokeMethod(view->rootItem(), "stop");
}
m_shutdownTimer->start(animationDuration());
m_status = Status::Inactive;
}
void OverviewEffect::realDeactivate()
{
setRunning(false);
m_status = Status::Inactive;
}
void OverviewEffect::quickDeactivate()
......
......@@ -19,8 +19,16 @@ class OverviewEffect : public QuickSceneEffect
Q_PROPERTY(int animationDuration READ animationDuration NOTIFY animationDurationChanged)
Q_PROPERTY(ExpoLayout::LayoutMode layout READ layout NOTIFY layoutChanged)
Q_PROPERTY(bool blurBackground READ blurBackground NOTIFY blurBackgroundChanged)
Q_PROPERTY(qreal partialActivationFactor READ partialActivationFactor NOTIFY partialActivationFactorChanged)
// More efficient from a property binding pov rather than binding to partialActivationFactor !== 0
Q_PROPERTY(bool gestureInProgress READ gestureInProgress NOTIFY gestureInProgressChanged)
public:
enum class Status {
Inactive,
Activating,
Active
};
OverviewEffect();
~OverviewEffect() override;
......@@ -33,6 +41,9 @@ public:
bool blurBackground() const;
void setBlurBackground(bool blur);
qreal partialActivationFactor() const;
bool gestureInProgress() const;
int requestedEffectChainPosition() const override;
bool borderActivated(ElectricBorder border) override;
void reconfigure(ReconfigureFlags flags) override;
......@@ -42,9 +53,12 @@ Q_SIGNALS:
void animationDurationChanged();
void layoutChanged();
void blurBackgroundChanged();
void partialActivationFactorChanged();
void gestureInProgressChanged();
public Q_SLOTS:
void activate();
void partialActivate();
void deactivate();
void quickDeactivate();
void toggle();
......@@ -60,7 +74,9 @@ private:
QList<QKeySequence> m_toggleShortcut;
QList<ElectricBorder> m_borderActivate;
QList<ElectricBorder> m_touchBorderActivate;
qreal m_partialActivationFactor = 0;
bool m_blurBackground = false;
Status m_status = Status::Inactive;
int m_animationDuration = 200;
ExpoLayout::LayoutMode m_layout = ExpoLayout::LayoutNatural;
};
......
......@@ -41,17 +41,81 @@ FocusScope {
Keys.forwardTo: searchField
KWinComponents.DesktopBackgroundItem {
id: backgroundItem
activity: KWinComponents.Workspace.currentActivity
desktop: KWinComponents.Workspace.currentVirtualDesktop
outputName: targetScreen.name
property real blurRadius: 0
layer.enabled: effect.blurBackground
layer.effect: FastBlur {
radius: container.organized ? 64 : 0
radius: backgroundItem.blurRadius
}
}
state: {
if (effect.gestureInProgress) {
return "partial";
} else if (container.organized) {
return "active";
} else {
return "initial";
}
}
Behavior on radius {
NumberAnimation { duration: effect.animationDuration; easing.type: Easing.OutCubic }
states: [
State {
name: "initial"
PropertyChanges {
target: underlay
opacity: 0
}
PropertyChanges {
target: topBar
opacity: 0
}
PropertyChanges {
target: backgroundItem
blurRadius: 0
}
},
State {
name: "partial"
PropertyChanges {
target: underlay
opacity: 0.75 * effect.partialActivationFactor
}
PropertyChanges {
target: topBar
opacity: effect.partialActivationFactor
}
PropertyChanges {
target: backgroundItem
blurRadius: 64 * effect.partialActivationFactor
}
},
State {
name: "active"
PropertyChanges {
target: underlay
opacity: 0.75
}
PropertyChanges {
target: topBar
opacity: 1
}
PropertyChanges {
target: backgroundItem
blurRadius: 64
}
}
]
transitions: Transition {
to: "initial, active"
NumberAnimation {
duration: effect.animationDuration
properties: "opacity, blurRadius"
easing.type: Easing.OutCubic
}
}
......@@ -59,11 +123,6 @@ FocusScope {
id: underlay
anchors.fill: parent
color: PlasmaCore.ColorScope.backgroundColor
opacity: container.organized ? 0.75 : 0
Behavior on opacity {
OpacityAnimator { duration: effect.animationDuration; easing.type: Easing.OutCubic }
}
TapHandler {
onTapped: effect.deactivate();
......@@ -85,7 +144,6 @@ FocusScope {
id: topBar
width: parent.width
height: searchBar.height + desktopBar.height
opacity: container.organized ? 1 : 0
Rectangle {
id: desktopBar
......@@ -123,10 +181,6 @@ FocusScope {
onTextEdited: forceActiveFocus();
}
}
Behavior on opacity {
OpacityAnimator { duration: effect.animationDuration; easing.type: Easing.OutCubic }
}
}
Item {
......
......@@ -50,6 +50,9 @@ FocusScope {
readonly property bool selected: heap.selectedIndex == index
state: {
if (effect.gestureInProgress) {
return "partial";
}
if (heap.effectiveOrganized) {
return "active";
}
......@@ -108,10 +111,8 @@ FocusScope {
anchors.horizontalCenter: thumbSource.horizontalCenter
anchors.bottom: thumbSource.bottom
anchors.bottomMargin: -height / 4
opacity: heap.organized ? 1 : 0
visible: !dragHandler.active
TweenBehavior on opacity {}
PC3.Label {
id: caption
......@@ -146,6 +147,25 @@ FocusScope {
width: thumb.client.width
height: thumb.client.height
}
PropertyChanges {
target: icon
opacity: 0
}
},
State {
name: "partial"
PropertyChanges {
target: thumb
x: (thumb.client.x - targetScreen.geometry.x - expoLayout.Kirigami.ScenePosition.x) * (1 - effect.partialActivationFactor) + cell.x * effect.partialActivationFactor
y: (thumb.client.y - targetScreen.geometry.y - expoLayout.Kirigami.ScenePosition.y) * (1 - effect.partialActivationFactor) + cell.y * effect.partialActivationFactor
width: thumb.client.width * (1 - effect.partialActivationFactor) + cell.width * effect.partialActivationFactor
height: thumb.client.height * (1 - effect.partialActivationFactor) + cell.height * effect.partialActivationFactor
opacity: thumb.client.minimized ? effect.partialActivationFactor : 1
}
PropertyChanges {
target: icon
opacity: effect.partialActivationFactor
}
},
State {
name: "initial-minimized"
......@@ -154,6 +174,10 @@ FocusScope {
target: thumb
opacity: 0
}
PropertyChanges {
target: icon
opacity: 0
}
},
State {
name: "active"
......@@ -164,22 +188,23 @@ FocusScope {
width: cell.width
height: cell.height
}
PropertyChanges {
target: icon
opacity: 1
}
}
]
component TweenBehavior : Behavior {
enabled: heap.animationEnabled && !dragHandler.active
transitions: Transition {
to: "initial, active"
enabled: heap.animationEnabled
NumberAnimation {
duration: effect.animationDuration
properties: "x, y, width, height, opacity"
easing.type: Easing.OutCubic
}
}
TweenBehavior on x {}
TweenBehavior on y {}
TweenBehavior on width {}
TweenBehavior on height {}
TweenBehavior on opacity {}
PlasmaCore.FrameSvgItem {
anchors.fill: parent
......
......@@ -11,6 +11,7 @@
#include <QRect>
#include <functional>
#include <cmath>
#include <QDebug>
namespace KWin
{
......@@ -243,9 +244,12 @@ void GestureRecognizer::updateSwipeGesture(const QSizeF &delta)
auto g = static_cast<SwipeGesture *>(*it);
if (g->direction() != direction) {
Q_EMIT g->cancelled();
it = m_activeSwipeGestures.erase(it);
continue;
// If a gesture was started from a touchscreen border never cancel it
if (!g->minimumXIsRelevant() || !g->maximumXIsRelevant() || !g->minimumYIsRelevant() || !g->maximumYIsRelevant()) {
Q_EMIT g->cancelled();
it = m_activeSwipeGestures.erase(it);
continue;
}
}
it++;
......@@ -255,6 +259,7 @@ void GestureRecognizer::updateSwipeGesture(const QSizeF &delta)
// Send progress update
for (SwipeGesture *g: std::as_const(m_activeSwipeGestures)) {
Q_EMIT g->progress(g->minimumDeltaReachedProgress(m_currentDelta));
Q_EMIT g->deltaProgress(m_currentDelta);
}
}
......
......@@ -99,6 +99,11 @@ Q_SIGNALS:
*/
void progress(qreal);
/**
* The progress in actual pixel distance traveled by the fingers
*/
void deltaProgress(const QSizeF &delta);
private:
bool m_minimumFingerCountRelevant = false;
uint m_minimumFingerCount = 0;
......
......@@ -871,6 +871,8 @@ class KWINEFFECTS_EXPORT EffectsHandler : public QObject
friend class Effect;
public:
using TouchBorderCallback = std::function<void(ElectricBorder border, const QSizeF&, EffectScreen *screen)>;
explicit EffectsHandler(CompositingType type);
~EffectsHandler() override;
// for use by effects
......@@ -979,6 +981,25 @@ public:
* @since 5.10
*/
virtual void registerTouchBorder(ElectricBorder border, QAction *action) = 0;
/**
* Registers the given @p action for the given @p border to be activated through
* a touch swipe gesture.
*
* If the @p border gets triggered through a touch swipe gesture the QAction::triggered
* signal gets invoked.
*
* progressCallback will be dinamically called each time the touch position is updated
* to show the effect "partially" activated
*
* To unregister the touch screen action either delete the @p action or
* invoke unregisterTouchBorder.
*
* @see unregisterTouchBorder
* @since 5.25
*/
virtual void registerRealtimeTouchBorder(ElectricBorder border, QAction *action, TouchBorderCallback progressCallback) = 0;
/**
* Unregisters the given @p action for the given touch @p border.
*
......
......@@ -71,6 +71,7 @@ enum ElectricBorder {
ELECTRIC_COUNT,
ElectricNone,
};
Q_ENUM_NS(ElectricBorder)
// TODO: Hardcoding is bad, need to add some way of registering global actions to these.
// When designing the new system we must keep in mind that we have conditional actions
......
......@@ -21,6 +21,7 @@
// KWin
#include <config-kwin.h>
#include "abstract_output.h"
#include "effects.h"
#include "gestures.h"
#include <x11client.h>
#include "cursor.h"
......@@ -57,6 +58,31 @@ static const int TOUCH_TARGET = 3;
// How far the user needs to swipe before triggering an action.
static const int MINIMUM_DELTA = 44;
TouchCallback::TouchCallback(QAction *touchUpAction, TouchCallback::CallbackFunction progressCallback)
: m_touchUpAction(touchUpAction)
, m_progressCallback(progressCallback)
{}
TouchCallback::~TouchCallback()
{}
QAction *TouchCallback::touchUpAction() const
{
return m_touchUpAction;
}
void TouchCallback::progressCallback(ElectricBorder border, const QSizeF &deltaProgress, AbstractOutput *output) const
{
if (m_progressCallback) {
m_progressCallback(border, deltaProgress, output);
}
}
bool TouchCallback::hasProgressCallback() const
{
return m_progressCallback != nullptr;
}
Edge::Edge(ScreenEdges *parent)
: QObject(parent)
, m_edges(parent)
......@@ -68,6 +94,7 @@ Edge::Edge(ScreenEdges *parent)
, m_blocked(false)
, m_pushBackBlocked(false)
, m_client(nullptr)
, m_output(nullptr)
, m_gesture(new SwipeGesture(this))
{
m_gesture->setMinimumFingerCount(1);
......@@ -86,6 +113,12 @@ Edge::Edge(ScreenEdges *parent)
);
connect(m_gesture, &SwipeGesture::started, this, &Edge::startApproaching);
connect(m_gesture, &SwipeGesture::cancelled, this, &Edge::stopApproaching);
connect(m_gesture, &SwipeGesture::cancelled, this, [this] () {
if (!m_touchCallbacks.isEmpty() && m_touchCallbacks.constFirst().hasProgressCallback()) {
handleTouchCallback();
}
}
);
connect(m_gesture, &SwipeGesture::progress, this,
[this] (qreal progress) {
int factor = progress * 256.0f;
......@@ -95,6 +128,13 @@ Edge::Edge(ScreenEdges *parent)
}
}
);
connect(m_gesture, &SwipeGesture::deltaProgress, this,
[this] (const QSizeF &progressDelta) {
if (!m_touchCallbacks.isEmpty()) {
m_touchCallbacks.constFirst().progressCallback(border(), progressDelta, m_output);
}
}
);
connect(this, &Edge::activatesForTouchGestureChanged, this,
[this] {
if (isReserved()) {
......@@ -128,25 +168,33 @@ void Edge::reserve(QObject *object, const char *slot)
reserve();
}
void Edge::reserveTouchCallBack(QAction *action)
void Edge::reserveTouchCallBack(QAction *action, TouchCallback::CallbackFunction callback)
{
if (auto itr = std::find_if(m_touchCallbacks.constBegin(), m_touchCallbacks.constEnd(), [action] (const TouchCallback &c) { return c.touchUpAction() == action; }); itr != m_touchCallbacks.constEnd()) {
return;
}
reserveTouchCallBack(TouchCallback(action, callback));
}
void Edge::reserveTouchCallBack(const TouchCallback &callback)
{
if (m_touchActions.contains(action)) {
if (auto itr = std::find_if(m_touchCallbacks.constBegin(), m_touchCallbacks.constEnd(), [callback] (const TouchCallback &c) { return c.touchUpAction() == callback.touchUpAction(); }); itr != m_touchCallbacks.constEnd()) {
return;
}
connect(action, &QAction::destroyed, this,
[this, action] {
unreserveTouchCallBack(action);
connect(callback.touchUpAction(), &QAction::destroyed, this,
[this, callback] {
unreserveTouchCallBack(callback.touchUpAction());
}
);
m_touchActions << action;
m_touchCallbacks << callback;
reserve();
}
void Edge::unreserveTouchCallBack(QAction *action)
{
auto it = std::find_if(m_touchActions.begin(), m_touchActions.end(), [action] (QAction *a) { return a == action; });
if (it != m_touchActions.end()) {
m_touchActions.erase(it);
auto it = std::find_if(m_touchCallbacks.begin(), m_touchCallbacks.end(), [action] (const TouchCallback &c) { return c.touchUpAction() == action; });
if (it != m_touchCallbacks.end()) {
m_touchCallbacks.erase(it);
unreserve();
}
}
......@@ -206,7 +254,7 @@ bool Edge::activatesForTouchGesture() const
if (m_touchAction != ElectricActionNone) {
return true;
}
if (!m_touchActions.isEmpty()) {
if (!m_touchCallbacks.isEmpty()) {
return true;
}
return false;
......@@ -399,10 +447,9 @@ bool Edge::handleByCallback()
void Edge::handleTouchCallback()