File indexing completed on 2025-07-13 05:03:00
0001 // SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org> 0002 // SPDX-FileCopyrightText: 2020 The Qt Company Ltd. 0003 // SPDX-License-Identifier: GPL-2.0-or-later 0004 0005 // based on Flickable, but heavily simplified 0006 0007 #include "swipearea.h" 0008 0009 #include <QMouseEvent> 0010 #include <QObject> 0011 #include <QTabletEvent> 0012 #include <QTouchEvent> 0013 0014 // how many pixels to move before it starts being registered as a swipe 0015 const int SWIPE_REGISTER_THRESHOLD = 10; 0016 0017 SwipeArea::SwipeArea(QQuickItem *parent) 0018 : QQuickItem{parent} 0019 { 0020 setAcceptTouchEvents(true); 0021 setAcceptedMouseButtons(Qt::LeftButton); 0022 setFiltersChildMouseEvents(true); 0023 } 0024 0025 SwipeArea::Mode SwipeArea::mode() const 0026 { 0027 return m_mode; 0028 } 0029 0030 void SwipeArea::setMode(Mode mode) 0031 { 0032 m_mode = mode; 0033 Q_EMIT modeChanged(); 0034 } 0035 0036 bool SwipeArea::interactive() const 0037 { 0038 return m_interactive; 0039 } 0040 0041 void SwipeArea::setInteractive(bool interactive) 0042 { 0043 m_interactive = interactive; 0044 Q_EMIT interactiveChanged(); 0045 } 0046 0047 bool SwipeArea::moving() const 0048 { 0049 return m_moving; 0050 } 0051 0052 bool SwipeArea::pressed() const 0053 { 0054 return m_pressed; 0055 } 0056 0057 void SwipeArea::setSkipSwipeThreshold(bool value) 0058 { 0059 m_skipSwipeThreshold = value; 0060 } 0061 0062 bool SwipeArea::childMouseEventFilter(QQuickItem *item, QEvent *event) 0063 { 0064 if (!isVisible() || !isEnabled() || !m_interactive) { 0065 resetSwipe(); 0066 return QQuickItem::childMouseEventFilter(item, event); 0067 } 0068 0069 if (event->isPointerEvent() && event->type() != QEvent::UngrabMouse) { 0070 return filterPointerEvent(item, static_cast<QPointerEvent *>(event)); 0071 } 0072 0073 return QQuickItem::childMouseEventFilter(item, event); 0074 } 0075 0076 // take exclusive grab from children 0077 bool SwipeArea::filterPointerEvent(QQuickItem *receiver, QPointerEvent *event) 0078 { 0079 // only filter mouse, touch or tablet events 0080 if (!dynamic_cast<QMouseEvent *>(event) && !dynamic_cast<QTabletEvent *>(event) && !dynamic_cast<QTouchEvent *>(event)) { 0081 return false; 0082 } 0083 0084 const auto &firstPoint = event->points().first(); 0085 0086 if (event->pointCount() == 1 && event->exclusiveGrabber(firstPoint) == this) { 0087 // We have an exclusive grab (since we're e.g dragging), but at the same time, we have 0088 // a child with a passive grab (which is why this filter is being called). And because 0089 // of that, we end up getting the same pointer events twice; First in our own event 0090 // handlers (because of the grab), then once more in here, since we filter the child. 0091 // To avoid processing the event twice (e.g avoid calling handleReleaseEvent once more 0092 // from below), we mark the event as filtered, and simply return. 0093 event->setAccepted(true); 0094 return true; 0095 } 0096 0097 QPointF localPos = mapFromScene(firstPoint.scenePosition()); 0098 bool receiverDisabled = receiver && !receiver->isEnabled(); 0099 bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab()); 0100 0101 if ((m_stealMouse || contains(localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) { 0102 // clone the event, and set the first point's local position 0103 // HACK: we can't change QPointerEvent's points since it's const, so we have to pass localPos into the handlers 0104 QPointerEvent *localizedEvent = event->clone(); 0105 localizedEvent->setAccepted(false); 0106 0107 switch (firstPoint.state()) { 0108 case QEventPoint::State::Updated: 0109 handleMoveEvent(localizedEvent, localPos); 0110 break; 0111 case QEventPoint::State::Pressed: 0112 handlePressEvent(localizedEvent, localPos); 0113 break; 0114 case QEventPoint::State::Released: 0115 handleReleaseEvent(localizedEvent, localPos); 0116 break; 0117 case QEventPoint::State::Stationary: 0118 case QEventPoint::State::Unknown: 0119 break; 0120 } 0121 0122 if ((receiver && m_stealMouse && !receiverKeepsGrab && receiver != this) || receiverDisabled) { 0123 event->setExclusiveGrabber(firstPoint, this); 0124 } 0125 0126 bool filtered = m_stealMouse || receiverDisabled; 0127 if (filtered) { 0128 event->setAccepted(true); 0129 } 0130 0131 return filtered; 0132 } 0133 0134 if (firstPoint.state() == QEventPoint::State::Released || (receiverKeepsGrab && !receiverDisabled)) { 0135 // mouse released, or another item has claimed the grab 0136 resetSwipe(); 0137 } 0138 0139 return false; 0140 } 0141 0142 void SwipeArea::mouseMoveEvent(QMouseEvent *event) 0143 { 0144 if (m_interactive) { 0145 handleMoveEvent(event, event->points().first().position()); 0146 event->accept(); 0147 } else { 0148 QQuickItem::mouseMoveEvent(event); 0149 } 0150 } 0151 0152 void SwipeArea::mousePressEvent(QMouseEvent *event) 0153 { 0154 if (m_interactive) { 0155 handlePressEvent(event, event->points().first().position()); 0156 event->accept(); 0157 } else { 0158 QQuickItem::mousePressEvent(event); 0159 } 0160 } 0161 0162 void SwipeArea::mouseReleaseEvent(QMouseEvent *event) 0163 { 0164 if (m_interactive) { 0165 handleReleaseEvent(event, event->points().first().position()); 0166 event->accept(); 0167 } else { 0168 QQuickItem::mouseReleaseEvent(event); 0169 } 0170 } 0171 0172 void SwipeArea::mouseUngrabEvent() 0173 { 0174 QQuickItem::mouseUngrabEvent(); 0175 } 0176 0177 void SwipeArea::touchEvent(QTouchEvent *event) 0178 { 0179 bool unhandled = true; 0180 const auto &firstPoint = event->points().first(); 0181 0182 switch (firstPoint.state()) { 0183 case QEventPoint::State::Pressed: 0184 if (m_interactive) { 0185 handlePressEvent(event, firstPoint.position()); 0186 event->accept(); 0187 unhandled = false; 0188 } 0189 break; 0190 case QEventPoint::State::Updated: 0191 if (m_interactive) { 0192 handleMoveEvent(event, firstPoint.position()); 0193 event->accept(); 0194 unhandled = false; 0195 } 0196 break; 0197 case QEventPoint::State::Released: 0198 if (m_interactive) { 0199 handleReleaseEvent(event, firstPoint.position()); 0200 event->accept(); 0201 unhandled = false; 0202 } 0203 break; 0204 case QEventPoint::State::Stationary: 0205 case QEventPoint::State::Unknown: 0206 break; 0207 } 0208 0209 if (unhandled) { 0210 QQuickItem::touchEvent(event); 0211 } 0212 } 0213 0214 void SwipeArea::touchUngrabEvent() 0215 { 0216 QQuickItem::touchUngrabEvent(); 0217 } 0218 0219 void SwipeArea::setMoving(bool moving) 0220 { 0221 m_moving = moving; 0222 Q_EMIT movingChanged(); 0223 } 0224 0225 void SwipeArea::setPressed(bool pressed) 0226 { 0227 m_pressed = pressed; 0228 Q_EMIT pressedChanged(); 0229 } 0230 0231 void SwipeArea::resetSwipe() 0232 { 0233 m_skipSwipeThreshold = false; 0234 m_stealMouse = false; 0235 if (m_pressed) { 0236 setPressed(false); 0237 } 0238 if (m_moving) { 0239 setMoving(false); 0240 } 0241 } 0242 0243 void SwipeArea::handlePressEvent(QPointerEvent *event, QPointF point) 0244 { 0245 // ignore more touch events 0246 if (m_pressed) { 0247 return; 0248 } 0249 0250 setPressed(true); 0251 m_stealMouse = false; 0252 m_pressPos = point; 0253 m_lastPos = m_pressPos; 0254 } 0255 0256 void SwipeArea::handleReleaseEvent(QPointerEvent *event, QPointF point) 0257 { 0258 // if we are in a swipe 0259 if (m_moving) { 0260 Q_EMIT swipeEnded(); 0261 } 0262 0263 resetSwipe(); 0264 } 0265 0266 void SwipeArea::handleMoveEvent(QPointerEvent *event, QPointF point) 0267 { 0268 if (!m_stealMouse) { 0269 if (!m_skipSwipeThreshold) { 0270 // if we haven't reached the swipe registering threshold yet, don't start the swipe 0271 if (m_mode == Mode::VerticalOnly && qAbs(point.y() - m_pressPos.y()) < SWIPE_REGISTER_THRESHOLD) { 0272 return; 0273 } else if (m_mode == Mode::HorizontalOnly && qAbs(point.x() - m_pressPos.x()) < SWIPE_REGISTER_THRESHOLD) { 0274 return; 0275 } else if (m_mode == Mode::BothAxis && qAbs(point.manhattanLength() - m_pressPos.manhattanLength()) < SWIPE_REGISTER_THRESHOLD) { 0276 return; 0277 } 0278 } 0279 m_skipSwipeThreshold = false; 0280 0281 // we now start the swipe, stealing it from children 0282 0283 m_startPos = point; 0284 m_lastPos = point; 0285 m_stealMouse = true; 0286 setMoving(true); 0287 Q_EMIT swipeStarted(m_startPos); 0288 } 0289 0290 const QVector2D totalDelta = QVector2D(point - m_startPos); 0291 const QVector2D delta = QVector2D(point - m_lastPos); 0292 m_lastPos = point; 0293 0294 // ensure it's called AFTER swipeStarted() 0295 Q_EMIT swipeMove(totalDelta.x(), totalDelta.y(), delta.x(), delta.y()); 0296 }