/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2017 Martin Gräßlin SPDX-License-Identifier: GPL-2.0-or-later */ #include "gestures.h" #include #include #include #include namespace KWin { Gesture::Gesture(QObject *parent) : QObject(parent) { } Gesture::~Gesture() = default; SwipeGesture::SwipeGesture(QObject *parent) : Gesture(parent) { } SwipeGesture::~SwipeGesture() = default; void SwipeGesture::setStartGeometry(const QRect &geometry) { setMinimumX(geometry.x()); setMinimumY(geometry.y()); setMaximumX(geometry.x() + geometry.width()); setMaximumY(geometry.y() + geometry.height()); Q_ASSERT(m_maximumX >= m_minimumX); Q_ASSERT(m_maximumY >= m_minimumY); } qreal SwipeGesture::deltaToProgress(const QSizeF &delta) const { if (!m_minimumDeltaRelevant || m_minimumDelta.isNull()) { return 1.0; } switch (m_direction) { case Direction::Up: case Direction::Down: return std::min(std::abs(delta.height()) / std::abs(m_minimumDelta.height()), 1.0); case Direction::Left: case Direction::Right: return std::min(std::abs(delta.width()) / std::abs(m_minimumDelta.width()), 1.0); default: Q_UNREACHABLE(); } } bool SwipeGesture::minimumDeltaReached(const QSizeF &delta) const { return deltaToProgress(delta) >= 1.0; } PinchGesture::PinchGesture(QObject *parent) : Gesture(parent) { } PinchGesture::~PinchGesture() = default; qreal PinchGesture::scaleDeltaToProgress(const qreal &scaleDelta) const { return std::clamp(std::abs(scaleDelta - 1) / minimumScaleDelta(), 0.0, 1.0); } bool PinchGesture::minimumScaleDeltaReached(const qreal &scaleDelta) const { return scaleDeltaToProgress(scaleDelta) >= 1.0; } GestureRecognizer::GestureRecognizer(QObject *parent) : QObject(parent) { } GestureRecognizer::~GestureRecognizer() = default; void GestureRecognizer::registerSwipeGesture(KWin::SwipeGesture *gesture) { Q_ASSERT(!m_swipeGestures.contains(gesture)); auto connection = connect(gesture, &QObject::destroyed, this, std::bind(&GestureRecognizer::unregisterSwipeGesture, this, gesture)); m_destroyConnections.insert(gesture, connection); m_swipeGestures << gesture; } void GestureRecognizer::unregisterSwipeGesture(KWin::SwipeGesture *gesture) { auto it = m_destroyConnections.find(gesture); if (it != m_destroyConnections.end()) { disconnect(it.value()); m_destroyConnections.erase(it); } m_swipeGestures.removeAll(gesture); if (m_activeSwipeGestures.removeOne(gesture)) { Q_EMIT gesture->cancelled(); } } void GestureRecognizer::registerPinchGesture(KWin::PinchGesture *gesture) { Q_ASSERT(!m_pinchGestures.contains(gesture)); auto connection = connect(gesture, &QObject::destroyed, this, std::bind(&GestureRecognizer::unregisterPinchGesture, this, gesture)); m_destroyConnections.insert(gesture, connection); m_pinchGestures << gesture; } void GestureRecognizer::unregisterPinchGesture(KWin::PinchGesture *gesture) { auto it = m_destroyConnections.find(gesture); if (it != m_destroyConnections.end()) { disconnect(it.value()); m_destroyConnections.erase(it); } m_pinchGestures.removeAll(gesture); if (m_activePinchGestures.removeOne(gesture)) { Q_EMIT gesture->cancelled(); } } int GestureRecognizer::startSwipeGesture(uint fingerCount, const QPointF &startPos, StartPositionBehavior startPosBehavior) { m_currentFingerCount = fingerCount; if (!m_activeSwipeGestures.isEmpty() || !m_activePinchGestures.isEmpty()) { return 0; } int count = 0; for (SwipeGesture *gesture : qAsConst(m_swipeGestures)) { if (gesture->minimumFingerCountIsRelevant()) { if (gesture->minimumFingerCount() > fingerCount) { continue; } } if (gesture->maximumFingerCountIsRelevant()) { if (gesture->maximumFingerCount() < fingerCount) { continue; } } if (startPosBehavior == StartPositionBehavior::Relevant) { if (gesture->minimumXIsRelevant()) { if (gesture->minimumX() > startPos.x()) { continue; } } if (gesture->maximumXIsRelevant()) { if (gesture->maximumX() < startPos.x()) { continue; } } if (gesture->minimumYIsRelevant()) { if (gesture->minimumY() > startPos.y()) { continue; } } if (gesture->maximumYIsRelevant()) { if (gesture->maximumY() < startPos.y()) { continue; } } } // Only add gestures who's direction aligns with current swipe axis switch (gesture->direction()) { case SwipeGesture::Direction::Up: case SwipeGesture::Direction::Down: if (m_currentSwipeAxis == Axis::Horizontal) { continue; } break; case SwipeGesture::Direction::Left: case SwipeGesture::Direction::Right: if (m_currentSwipeAxis == Axis::Vertical) { continue; } break; } m_activeSwipeGestures << gesture; count++; Q_EMIT gesture->started(); } return count; } void GestureRecognizer::updateSwipeGesture(const QSizeF &delta) { m_currentDelta += delta; SwipeGesture::Direction direction; // Overall direction Axis swipeAxis; // Pick an axis for gestures so horizontal ones don't change to vertical ones without lifting fingers if (m_currentSwipeAxis == Axis::None) { if (std::abs(m_currentDelta.width()) >= std::abs(m_currentDelta.height())) { swipeAxis = Axis::Horizontal; direction = m_currentDelta.width() < 0 ? SwipeGesture::Direction::Left : SwipeGesture::Direction::Right; } else { swipeAxis = Axis::Vertical; direction = m_currentDelta.height() < 0 ? SwipeGesture::Direction::Up : SwipeGesture::Direction::Down; } if (std::abs(m_currentDelta.width()) >= 5 || std::abs(m_currentDelta.height()) >= 5) { // only lock in a direction if the delta is big enough // to prevent accidentally choosing the wrong direction m_currentSwipeAxis = swipeAxis; } } else { swipeAxis = m_currentSwipeAxis; } // Find the current swipe direction switch (swipeAxis) { case Axis::Vertical: direction = m_currentDelta.height() < 0 ? SwipeGesture::Direction::Up : SwipeGesture::Direction::Down; break; case Axis::Horizontal: direction = m_currentDelta.width() < 0 ? SwipeGesture::Direction::Left : SwipeGesture::Direction::Right; break; default: Q_UNREACHABLE(); } // Eliminate wrong gestures (takes two iterations) for (int i = 0; i < 2; i++) { if (m_activeSwipeGestures.isEmpty()) { startSwipeGesture(m_currentFingerCount); } for (auto it = m_activeSwipeGestures.begin(); it != m_activeSwipeGestures.end();) { auto g = static_cast(*it); if (g->direction() != direction) { // 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++; } } // Send progress update for (SwipeGesture *g : std::as_const(m_activeSwipeGestures)) { Q_EMIT g->progress(g->deltaToProgress(m_currentDelta)); Q_EMIT g->deltaProgress(m_currentDelta); } } void GestureRecognizer::cancelActiveGestures() { for (auto g : qAsConst(m_activeSwipeGestures)) { Q_EMIT g->cancelled(); } for (auto g : qAsConst(m_activePinchGestures)) { Q_EMIT g->cancelled(); } m_activeSwipeGestures.clear(); m_activePinchGestures.clear(); m_currentScale = 0; m_currentDelta = QSizeF(0, 0); m_currentSwipeAxis = Axis::None; } void GestureRecognizer::cancelSwipeGesture() { cancelActiveGestures(); m_currentFingerCount = 0; m_currentDelta = QSizeF(0, 0); m_currentSwipeAxis = Axis::None; } void GestureRecognizer::endSwipeGesture() { const QSizeF delta = m_currentDelta; for (auto g : qAsConst(m_activeSwipeGestures)) { if (static_cast(g)->minimumDeltaReached(delta)) { Q_EMIT g->triggered(); } else { Q_EMIT g->cancelled(); } } m_activeSwipeGestures.clear(); m_currentFingerCount = 0; m_currentDelta = QSizeF(0, 0); m_currentSwipeAxis = Axis::None; } int GestureRecognizer::startPinchGesture(uint fingerCount) { m_currentFingerCount = fingerCount; int count = 0; if (!m_activeSwipeGestures.isEmpty() || !m_activePinchGestures.isEmpty()) { return 0; } for (PinchGesture *gesture : qAsConst(m_pinchGestures)) { if (gesture->minimumFingerCountIsRelevant()) { if (gesture->minimumFingerCount() > fingerCount) { continue; } } if (gesture->maximumFingerCountIsRelevant()) { if (gesture->maximumFingerCount() < fingerCount) { continue; } } // direction doesn't matter yet m_activePinchGestures << gesture; count++; Q_EMIT gesture->started(); } return count; } void GestureRecognizer::updatePinchGesture(qreal scale, qreal angleDelta, const QSizeF &posDelta) { Q_UNUSED(angleDelta); Q_UNUSED(posDelta); m_currentScale = scale; // Determine the direction of the swipe PinchGesture::Direction direction; if (scale < 1) { direction = PinchGesture::Direction::Contracting; } else { direction = PinchGesture::Direction::Expanding; } // Eliminate wrong gestures (takes two iterations) for (int i = 0; i < 2; i++) { if (m_activePinchGestures.isEmpty()) { startPinchGesture(m_currentFingerCount); } for (auto it = m_activePinchGestures.begin(); it != m_activePinchGestures.end();) { auto g = static_cast(*it); if (g->direction() != direction) { Q_EMIT g->cancelled(); it = m_activePinchGestures.erase(it); continue; } it++; } } for (PinchGesture *g : std::as_const(m_activePinchGestures)) { Q_EMIT g->progress(g->scaleDeltaToProgress(scale)); } } void GestureRecognizer::cancelPinchGesture() { cancelActiveGestures(); m_currentScale = 1; m_currentFingerCount = 0; m_currentSwipeAxis = Axis::None; } void GestureRecognizer::endPinchGesture() // because fingers up { for (auto g : qAsConst(m_activePinchGestures)) { if (g->minimumScaleDeltaReached(m_currentScale)) { Q_EMIT g->triggered(); } else { Q_EMIT g->cancelled(); } } m_activeSwipeGestures.clear(); m_activePinchGestures.clear(); m_currentScale = 1; m_currentFingerCount = 0; m_currentSwipeAxis = Axis::None; } bool SwipeGesture::maximumFingerCountIsRelevant() const { return m_maximumFingerCountRelevant; } uint SwipeGesture::minimumFingerCount() const { return m_minimumFingerCount; } void SwipeGesture::setMinimumFingerCount(uint count) { m_minimumFingerCount = count; m_minimumFingerCountRelevant = true; } bool SwipeGesture::minimumFingerCountIsRelevant() const { return m_minimumFingerCountRelevant; } void SwipeGesture::setMaximumFingerCount(uint count) { m_maximumFingerCount = count; m_maximumFingerCountRelevant = true; } uint SwipeGesture::maximumFingerCount() const { return m_maximumFingerCount; } SwipeGesture::Direction SwipeGesture::direction() const { return m_direction; } void SwipeGesture::setDirection(Direction direction) { m_direction = direction; } void SwipeGesture::setMinimumX(int x) { m_minimumX = x; m_minimumXRelevant = true; } int SwipeGesture::minimumX() const { return m_minimumX; } bool SwipeGesture::minimumXIsRelevant() const { return m_minimumXRelevant; } void SwipeGesture::setMinimumY(int y) { m_minimumY = y; m_minimumYRelevant = true; } int SwipeGesture::minimumY() const { return m_minimumY; } bool SwipeGesture::minimumYIsRelevant() const { return m_minimumYRelevant; } void SwipeGesture::setMaximumX(int x) { m_maximumX = x; m_maximumXRelevant = true; } int SwipeGesture::maximumX() const { return m_maximumX; } bool SwipeGesture::maximumXIsRelevant() const { return m_maximumXRelevant; } void SwipeGesture::setMaximumY(int y) { m_maximumY = y; m_maximumYRelevant = true; } int SwipeGesture::maximumY() const { return m_maximumY; } bool SwipeGesture::maximumYIsRelevant() const { return m_maximumYRelevant; } QSizeF SwipeGesture::minimumDelta() const { return m_minimumDelta; } void SwipeGesture::setMinimumDelta(const QSizeF &delta) { m_minimumDelta = delta; m_minimumDeltaRelevant = true; } bool SwipeGesture::isMinimumDeltaRelevant() const { return m_minimumDeltaRelevant; } bool PinchGesture::minimumFingerCountIsRelevant() const { return m_minimumFingerCountRelevant; } void PinchGesture::setMinimumFingerCount(uint count) { m_minimumFingerCount = count; m_minimumFingerCountRelevant = true; } uint PinchGesture::minimumFingerCount() const { return m_minimumFingerCount; } bool PinchGesture::maximumFingerCountIsRelevant() const { return m_maximumFingerCountRelevant; } void PinchGesture::setMaximumFingerCount(uint count) { m_maximumFingerCount = count; m_maximumFingerCountRelevant = true; } uint PinchGesture::maximumFingerCount() const { return m_maximumFingerCount; } PinchGesture::Direction PinchGesture::direction() const { return m_direction; } void PinchGesture::setDirection(Direction direction) { m_direction = direction; } qreal PinchGesture::minimumScaleDelta() const { return m_minimumScaleDelta; } void PinchGesture::setMinimumScaleDelta(const qreal &scaleDelta) { m_minimumScaleDelta = scaleDelta; m_minimumScaleDeltaRelevant = true; } bool PinchGesture::isMinimumScaleDeltaRelevant() const { return m_minimumScaleDeltaRelevant; } int GestureRecognizer::startSwipeGesture(uint fingerCount) { return startSwipeGesture(fingerCount, QPointF(), StartPositionBehavior::Irrelevant); } int GestureRecognizer::startSwipeGesture(const QPointF &startPos) { return startSwipeGesture(1, startPos, StartPositionBehavior::Relevant); } }