mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 22:33:08 +00:00
349 lines
9.3 KiB
C++
349 lines
9.3 KiB
C++
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
|
// SPDX-FileCopyrightText: 2020 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
// based on Flickable, but heavily simplified
|
|
|
|
#include "swipearea.h"
|
|
|
|
#include <QMouseEvent>
|
|
#include <QObject>
|
|
#include <QTabletEvent>
|
|
#include <QTouchEvent>
|
|
|
|
// how many pixels to move before it starts being registered as a swipe
|
|
const int SWIPE_REGISTER_THRESHOLD = 10;
|
|
|
|
SwipeArea::SwipeArea(QQuickItem *parent)
|
|
: QQuickItem{parent}
|
|
{
|
|
setAcceptTouchEvents(true);
|
|
setAcceptedMouseButtons(Qt::LeftButton);
|
|
setFiltersChildMouseEvents(true);
|
|
}
|
|
|
|
SwipeArea::Mode SwipeArea::mode() const
|
|
{
|
|
return m_mode;
|
|
}
|
|
|
|
void SwipeArea::setMode(Mode mode)
|
|
{
|
|
m_mode = mode;
|
|
Q_EMIT modeChanged();
|
|
}
|
|
|
|
bool SwipeArea::interactive() const
|
|
{
|
|
return m_interactive;
|
|
}
|
|
|
|
void SwipeArea::setInteractive(bool interactive)
|
|
{
|
|
m_interactive = interactive;
|
|
Q_EMIT interactiveChanged();
|
|
}
|
|
|
|
bool SwipeArea::moving() const
|
|
{
|
|
return m_moving;
|
|
}
|
|
|
|
bool SwipeArea::pressed() const
|
|
{
|
|
return m_pressed;
|
|
}
|
|
|
|
void SwipeArea::setSkipSwipeThreshold(bool value)
|
|
{
|
|
m_skipSwipeThreshold = value;
|
|
}
|
|
|
|
bool SwipeArea::childMouseEventFilter(QQuickItem *item, QEvent *event)
|
|
{
|
|
if (!isVisible() || !isEnabled() || !m_interactive) {
|
|
resetSwipe();
|
|
return QQuickItem::childMouseEventFilter(item, event);
|
|
}
|
|
|
|
if (event->isPointerEvent() && event->type() != QEvent::UngrabMouse) {
|
|
return filterPointerEvent(item, static_cast<QPointerEvent *>(event));
|
|
}
|
|
|
|
return QQuickItem::childMouseEventFilter(item, event);
|
|
}
|
|
|
|
// take exclusive grab from children
|
|
bool SwipeArea::filterPointerEvent(QQuickItem *receiver, QPointerEvent *event)
|
|
{
|
|
// only filter mouse, touch or tablet events
|
|
if (!dynamic_cast<QMouseEvent *>(event) && !dynamic_cast<QTabletEvent *>(event) && !dynamic_cast<QTouchEvent *>(event)) {
|
|
return false;
|
|
}
|
|
|
|
const auto &firstPoint = event->points().first();
|
|
|
|
if (event->pointCount() == 1 && event->exclusiveGrabber(firstPoint) == this) {
|
|
// We have an exclusive grab (since we're e.g dragging), but at the same time, we have
|
|
// a child with a passive grab (which is why this filter is being called). And because
|
|
// of that, we end up getting the same pointer events twice; First in our own event
|
|
// handlers (because of the grab), then once more in here, since we filter the child.
|
|
// To avoid processing the event twice (e.g avoid calling handleReleaseEvent once more
|
|
// from below), we mark the event as filtered, and simply return.
|
|
event->setAccepted(true);
|
|
return true;
|
|
}
|
|
|
|
QPointF localPos = mapFromScene(firstPoint.scenePosition());
|
|
bool receiverDisabled = receiver && !receiver->isEnabled();
|
|
bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab());
|
|
|
|
if ((m_stealMouse || contains(localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) {
|
|
// clone the event, and set the first point's local position
|
|
// HACK: we can't change QPointerEvent's points since it's const, so we have to pass localPos into the handlers
|
|
QPointerEvent *localizedEvent = event->clone();
|
|
localizedEvent->setAccepted(false);
|
|
|
|
switch (firstPoint.state()) {
|
|
case QEventPoint::State::Updated:
|
|
handleMoveEvent(localizedEvent, localPos);
|
|
break;
|
|
case QEventPoint::State::Pressed:
|
|
handlePressEvent(localizedEvent, localPos);
|
|
break;
|
|
case QEventPoint::State::Released:
|
|
handleReleaseEvent(localizedEvent, localPos);
|
|
break;
|
|
case QEventPoint::State::Stationary:
|
|
case QEventPoint::State::Unknown:
|
|
break;
|
|
}
|
|
|
|
if ((receiver && m_stealMouse && !receiverKeepsGrab && receiver != this) || receiverDisabled) {
|
|
event->setExclusiveGrabber(firstPoint, this);
|
|
}
|
|
|
|
bool filtered = m_stealMouse || receiverDisabled;
|
|
if (filtered) {
|
|
event->setAccepted(true);
|
|
}
|
|
|
|
return filtered;
|
|
}
|
|
|
|
if (firstPoint.state() == QEventPoint::State::Released || (receiverKeepsGrab && !receiverDisabled)) {
|
|
// mouse released, or another item has claimed the grab
|
|
resetSwipe();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SwipeArea::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
if (m_interactive) {
|
|
handleMoveEvent(event, event->points().first().position());
|
|
event->accept();
|
|
} else {
|
|
QQuickItem::mouseMoveEvent(event);
|
|
}
|
|
}
|
|
|
|
void SwipeArea::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
if (m_interactive) {
|
|
handlePressEvent(event, event->points().first().position());
|
|
event->accept();
|
|
} else {
|
|
QQuickItem::mousePressEvent(event);
|
|
}
|
|
}
|
|
|
|
void SwipeArea::mouseReleaseEvent(QMouseEvent *event)
|
|
{
|
|
if (m_interactive) {
|
|
handleReleaseEvent(event, event->points().first().position());
|
|
event->accept();
|
|
} else {
|
|
QQuickItem::mouseReleaseEvent(event);
|
|
}
|
|
}
|
|
|
|
void SwipeArea::mouseUngrabEvent()
|
|
{
|
|
QQuickItem::mouseUngrabEvent();
|
|
}
|
|
|
|
void SwipeArea::touchEvent(QTouchEvent *event)
|
|
{
|
|
bool unhandled = true;
|
|
const auto &firstPoint = event->points().first();
|
|
|
|
switch (firstPoint.state()) {
|
|
case QEventPoint::State::Pressed:
|
|
if (m_interactive) {
|
|
handlePressEvent(event, firstPoint.position());
|
|
event->accept();
|
|
unhandled = false;
|
|
}
|
|
break;
|
|
case QEventPoint::State::Updated:
|
|
if (m_interactive) {
|
|
handleMoveEvent(event, firstPoint.position());
|
|
event->accept();
|
|
unhandled = false;
|
|
}
|
|
break;
|
|
case QEventPoint::State::Released:
|
|
if (m_interactive) {
|
|
handleReleaseEvent(event, firstPoint.position());
|
|
event->accept();
|
|
unhandled = false;
|
|
}
|
|
break;
|
|
case QEventPoint::State::Stationary:
|
|
case QEventPoint::State::Unknown:
|
|
break;
|
|
}
|
|
|
|
if (unhandled) {
|
|
QQuickItem::touchEvent(event);
|
|
}
|
|
}
|
|
|
|
void SwipeArea::touchUngrabEvent()
|
|
{
|
|
QQuickItem::touchUngrabEvent();
|
|
}
|
|
|
|
void SwipeArea::wheelEvent(QWheelEvent *event)
|
|
{
|
|
if (!m_interactive) {
|
|
QQuickItem::wheelEvent(event);
|
|
return;
|
|
}
|
|
|
|
event->setAccepted(false);
|
|
|
|
switch (event->phase()) {
|
|
case Qt::ScrollBegin:
|
|
if (!m_touchpadScrolling) {
|
|
event->accept();
|
|
|
|
m_touchpadScrolling = true;
|
|
m_totalScrollDelta = QPointF{0, 0};
|
|
Q_EMIT touchpadScrollStarted(event->points().first().position());
|
|
}
|
|
break;
|
|
case Qt::ScrollEnd:
|
|
if (m_touchpadScrolling) {
|
|
m_touchpadScrolling = false;
|
|
m_totalScrollDelta = QPointF{0, 0};
|
|
Q_EMIT touchpadScrollEnded();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// HACK: if it isn't the touchpad, we never get the isBeginEvent() and isEndEvent() events
|
|
if (!m_touchpadScrolling) {
|
|
return;
|
|
}
|
|
|
|
for (auto &point : event->points()) {
|
|
event->addPassiveGrabber(point, this);
|
|
}
|
|
|
|
auto pixelDelta = event->pixelDelta();
|
|
m_totalScrollDelta = QPointF{m_totalScrollDelta + pixelDelta};
|
|
Q_EMIT touchpadScrollMove(m_totalScrollDelta.x(), m_totalScrollDelta.y(), pixelDelta.x(), pixelDelta.y());
|
|
|
|
event->accept();
|
|
}
|
|
|
|
void SwipeArea::setMoving(bool moving)
|
|
{
|
|
m_moving = moving;
|
|
Q_EMIT movingChanged();
|
|
}
|
|
|
|
void SwipeArea::setPressed(bool pressed)
|
|
{
|
|
m_pressed = pressed;
|
|
Q_EMIT pressedChanged();
|
|
}
|
|
|
|
void SwipeArea::resetSwipe()
|
|
{
|
|
m_skipSwipeThreshold = false;
|
|
m_stealMouse = false;
|
|
if (m_pressed) {
|
|
setPressed(false);
|
|
}
|
|
if (m_moving) {
|
|
setMoving(false);
|
|
}
|
|
}
|
|
|
|
void SwipeArea::handlePressEvent(QPointerEvent *event, QPointF point)
|
|
{
|
|
Q_UNUSED(event)
|
|
|
|
// ignore more touch events
|
|
if (m_pressed) {
|
|
return;
|
|
}
|
|
|
|
setPressed(true);
|
|
m_stealMouse = false;
|
|
m_pressPos = point;
|
|
m_lastPos = m_pressPos;
|
|
}
|
|
|
|
void SwipeArea::handleReleaseEvent(QPointerEvent *event, QPointF point)
|
|
{
|
|
Q_UNUSED(event)
|
|
Q_UNUSED(point)
|
|
|
|
// if we are in a swipe
|
|
if (m_moving) {
|
|
Q_EMIT swipeEnded();
|
|
}
|
|
|
|
resetSwipe();
|
|
}
|
|
|
|
void SwipeArea::handleMoveEvent(QPointerEvent *event, QPointF point)
|
|
{
|
|
Q_UNUSED(event)
|
|
|
|
if (!m_stealMouse) {
|
|
if (!m_skipSwipeThreshold) {
|
|
// if we haven't reached the swipe registering threshold yet, don't start the swipe
|
|
if (m_mode == Mode::VerticalOnly && qAbs(point.y() - m_pressPos.y()) < SWIPE_REGISTER_THRESHOLD) {
|
|
return;
|
|
} else if (m_mode == Mode::HorizontalOnly && qAbs(point.x() - m_pressPos.x()) < SWIPE_REGISTER_THRESHOLD) {
|
|
return;
|
|
} else if (m_mode == Mode::BothAxis && qAbs(point.manhattanLength() - m_pressPos.manhattanLength()) < SWIPE_REGISTER_THRESHOLD) {
|
|
return;
|
|
}
|
|
}
|
|
m_skipSwipeThreshold = false;
|
|
|
|
// we now start the swipe, stealing it from children
|
|
|
|
m_startPos = point;
|
|
m_lastPos = point;
|
|
m_stealMouse = true;
|
|
setMoving(true);
|
|
Q_EMIT swipeStarted(m_startPos, m_pressPos);
|
|
}
|
|
|
|
const QVector2D totalDelta = QVector2D(point - m_startPos);
|
|
const QVector2D delta = QVector2D(point - m_lastPos);
|
|
m_lastPos = point;
|
|
|
|
// ensure it's called AFTER swipeStarted()
|
|
Q_EMIT swipeMove(totalDelta.x(), totalDelta.y(), delta.x(), delta.y());
|
|
}
|