mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
components: Introduce SwipeArea component
This commit is contained in:
parent
58faa0362c
commit
2d37ef0771
4 changed files with 362 additions and 0 deletions
|
|
@ -8,6 +8,7 @@ set(mobileshellplugin_SRCS
|
|||
mobileshellplugin.cpp
|
||||
shellutil.cpp
|
||||
components/direction.cpp
|
||||
components/swipearea.cpp
|
||||
notifications/notificationthumbnailer.cpp
|
||||
notifications/notificationfilemenu.cpp
|
||||
)
|
||||
|
|
|
|||
268
components/mobileshell/components/swipearea.cpp
Normal file
268
components/mobileshell/components/swipearea.cpp
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
// 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);
|
||||
}
|
||||
|
||||
bool SwipeArea::interactive()
|
||||
{
|
||||
return m_interactive;
|
||||
}
|
||||
|
||||
bool SwipeArea::moving()
|
||||
{
|
||||
return m_moving;
|
||||
}
|
||||
|
||||
bool SwipeArea::pressed()
|
||||
{
|
||||
return m_pressed;
|
||||
}
|
||||
|
||||
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::setInteractive(bool interactive)
|
||||
{
|
||||
m_interactive = interactive;
|
||||
Q_EMIT interactiveChanged();
|
||||
}
|
||||
|
||||
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_stealMouse = false;
|
||||
if (m_pressed) {
|
||||
setPressed(false);
|
||||
}
|
||||
if (m_moving) {
|
||||
setMoving(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SwipeArea::handlePressEvent(QPointerEvent *event, QPointF point)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
resetSwipe();
|
||||
Q_EMIT swipeEnded();
|
||||
}
|
||||
|
||||
void SwipeArea::handleMoveEvent(QPointerEvent *event, QPointF point)
|
||||
{
|
||||
const QVector2D totalDelta = QVector2D(point - m_startPos);
|
||||
const QVector2D delta = QVector2D(point - m_lastPos);
|
||||
|
||||
m_lastPos = point;
|
||||
|
||||
if (!m_stealMouse) {
|
||||
// if we haven't reached the swipe registering threshold yet, don't start the swipe
|
||||
if (qAbs(point.manhattanLength() - m_pressPos.manhattanLength()) < SWIPE_REGISTER_THRESHOLD) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we now start the swipe, stealing it from children
|
||||
|
||||
m_startPos = point;
|
||||
m_stealMouse = true;
|
||||
setMoving(true);
|
||||
Q_EMIT swipeStarted(m_startPos);
|
||||
}
|
||||
|
||||
// ensure it's called AFTER swipeStarted()
|
||||
Q_EMIT swipeMove(totalDelta.x(), totalDelta.y(), delta.x(), delta.y());
|
||||
}
|
||||
91
components/mobileshell/components/swipearea.h
Normal file
91
components/mobileshell/components/swipearea.h
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <espidev@gmail.com>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPointF>
|
||||
#include <QPointerEvent>
|
||||
#include <QQmlListProperty>
|
||||
#include <QQuickItem>
|
||||
#include <QTouchEvent>
|
||||
|
||||
/**
|
||||
* @short A component that provides access to swipes over its children, similar to Flickable.
|
||||
* However, it does not do any of the positioning Flickable does, and so it
|
||||
* can be used to build custom components with specialized swiping needs (ex. panels)
|
||||
*
|
||||
* TODO: New fingers that come in should steal from the old finger
|
||||
*
|
||||
* @author Devin Lin <devin@kde.org>
|
||||
*/
|
||||
class SwipeArea : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool interactive READ interactive NOTIFY interactiveChanged)
|
||||
Q_PROPERTY(bool moving READ moving NOTIFY movingChanged)
|
||||
Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
|
||||
|
||||
QML_NAMED_ELEMENT(SwipeArea)
|
||||
|
||||
public:
|
||||
SwipeArea(QQuickItem *parent = nullptr);
|
||||
|
||||
bool interactive();
|
||||
bool moving();
|
||||
bool pressed();
|
||||
|
||||
Q_SIGNALS:
|
||||
void interactiveChanged();
|
||||
void movingChanged();
|
||||
void pressedChanged();
|
||||
|
||||
void swipeEnded();
|
||||
void swipeStarted(QPointF point);
|
||||
|
||||
// deltaX, deltaY - amount moved since last swipeMove()
|
||||
// totalDeltaX, totalDeltaY - amount move since startedSwipe()
|
||||
void swipeMove(qreal totalDeltaX, qreal totalDeltaY, qreal deltaX, qreal deltaY);
|
||||
|
||||
protected:
|
||||
bool childMouseEventFilter(QQuickItem *item, QEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseUngrabEvent() override;
|
||||
void touchEvent(QTouchEvent *event) override;
|
||||
void touchUngrabEvent() override;
|
||||
|
||||
private:
|
||||
void setInteractive(bool interactive);
|
||||
void setMoving(bool moving);
|
||||
void setPressed(bool pressed);
|
||||
|
||||
bool filterPointerEvent(QQuickItem *receiver, QPointerEvent *event);
|
||||
|
||||
void handlePressEvent(QPointerEvent *event, QPointF point);
|
||||
void handleReleaseEvent(QPointerEvent *event, QPointF point);
|
||||
void handleMoveEvent(QPointerEvent *event, QPointF point);
|
||||
|
||||
void resetSwipe();
|
||||
|
||||
bool m_interactive = true;
|
||||
bool m_pressed = false;
|
||||
|
||||
// whether we have started a flick
|
||||
bool m_moving = false;
|
||||
|
||||
// whether on this current flick, we want to steal the mouse/touch event from children
|
||||
bool m_stealMouse = false;
|
||||
|
||||
// the point where the user pressed down on at the start of the interaction
|
||||
QPointF m_pressPos;
|
||||
|
||||
// the point where the swipe actually started being registered (can be some distance from the pressed position)
|
||||
QPointF m_startPos;
|
||||
|
||||
// the previous point where interaction was at
|
||||
QPointF m_lastPos;
|
||||
};
|
||||
|
||||
QML_DECLARE_TYPE(SwipeArea)
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
#include <QQuickItem>
|
||||
|
||||
#include "components/direction.h"
|
||||
#include "components/swipearea.h"
|
||||
|
||||
#include "notifications/notificationfilemenu.h"
|
||||
#include "notifications/notificationthumbnailer.h"
|
||||
|
|
@ -31,6 +32,7 @@ void MobileShellPlugin::registerTypes(const char *uri)
|
|||
|
||||
// components
|
||||
qmlRegisterType<Direction>(uri, 1, 0, "Direction");
|
||||
qmlRegisterType<SwipeArea>(uri, 1, 0, "SwipeArea");
|
||||
|
||||
// notifications
|
||||
qmlRegisterType<NotificationThumbnailer>(uri, 1, 0, "NotificationThumbnailer");
|
||||
|
|
|
|||
Loading…
Reference in a new issue