diff --git a/YACReader/YACReader.pro b/YACReader/YACReader.pro index 08502677..04ac2aae 100644 --- a/YACReader/YACReader.pro +++ b/YACReader/YACReader.pro @@ -30,7 +30,8 @@ CONFIG(force_angle) { } } -SOURCES += main.cpp +SOURCES += main.cpp \ + viewer_gesture_handler.cpp INCLUDEPATH += ../common \ ../custom_widgets @@ -90,6 +91,7 @@ HEADERS += ../common/comic.h \ goto_flow_widget.h \ page_label_widget.h \ goto_flow_toolbar.h \ + viewer_gesture_handler.h \ width_slider.h \ notifications_label_widget.h \ ../common/pictureflow.h \ diff --git a/YACReader/main.cpp b/YACReader/main.cpp index 521c9dbf..f5677560 100644 --- a/YACReader/main.cpp +++ b/YACReader/main.cpp @@ -88,7 +88,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext &context, const QSt int main(int argc, char *argv[]) { - qInstallMessageHandler(messageHandler); + // qInstallMessageHandler(messageHandler); static const char ENV_VAR_QT_DEVICE_PIXEL_RATIO[] = "QT_DEVICE_PIXEL_RATIO"; if (!qEnvironmentVariableIsSet(ENV_VAR_QT_DEVICE_PIXEL_RATIO) && !qEnvironmentVariableIsSet("QT_AUTO_SCREEN_SCALE_FACTOR") && !qEnvironmentVariableIsSet("QT_SCALE_FACTOR") && !qEnvironmentVariableIsSet("QT_SCREEN_SCALE_FACTORS")) { @@ -97,6 +97,7 @@ int main(int argc, char *argv[]) QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); + QApplication::setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, true); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QImageReader::setAllocationLimit(0); diff --git a/YACReader/viewer.cpp b/YACReader/viewer.cpp index 34598e47..81f83a46 100644 --- a/YACReader/viewer.cpp +++ b/YACReader/viewer.cpp @@ -16,11 +16,13 @@ #include "notifications_label_widget.h" #include "comic_db.h" #include "shortcuts_manager.h" +#include "viewer_gesture_handler.h" #include "opengl_checker.h" #include #include +#include #include @@ -40,6 +42,9 @@ Viewer::Viewer(QWidget *parent) magnifyingGlassShown(false), restoreMagnifyingGlass(false) { + viewerGestureHandler = new ViewerGestureHandler(this); + viewerGestureHandler->setupGestureHandler(this); + translator = new YACReaderTranslator(this); translator->hide(); translatorAnimation = new QPropertyAnimation(translator, "pos"); @@ -727,6 +732,15 @@ void Viewer::mouseMoveEvent(QMouseEvent *event) } } +bool Viewer::event(QEvent *event) +{ + if (viewerGestureHandler->handleEvent(event)) { + return true; + } + + return QScrollArea::event(event); +} + QPixmap Viewer::pixmap() const { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) diff --git a/YACReader/viewer.h b/YACReader/viewer.h index 42b46b85..8920983e 100644 --- a/YACReader/viewer.h +++ b/YACReader/viewer.h @@ -30,6 +30,7 @@ class GoToFlowWidget; class Bookmarks; class PageLabelWidget; class NotificationsLabelWidget; +class ViewerGestureHandler; class Viewer : public QScrollArea, public ScrollManagement { @@ -153,6 +154,8 @@ private: bool shouldOpenNext; bool shouldOpenPrevious; + ViewerGestureHandler *viewerGestureHandler; + private: //! Magnifying glass MagnifyingGlass *mglass; @@ -164,6 +167,7 @@ private: void resizeEvent(QResizeEvent *event) override; void wheelEvent(QWheelEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; + bool event(QEvent *event) override; int verticalScrollStep() const; int horizontalScrollStep() const; diff --git a/YACReader/viewer_gesture_handler.cpp b/YACReader/viewer_gesture_handler.cpp new file mode 100644 index 00000000..ce541b0f --- /dev/null +++ b/YACReader/viewer_gesture_handler.cpp @@ -0,0 +1,430 @@ +#include "viewer_gesture_handler.h" + +#include +#include + +ViewerGestureHandler::ViewerGestureHandler(QObject *parent) + : QObject { parent }, oneFingerSwiperGestureRecognizer(new OneFingerSwipeGestureRecognizer()), twoFingerSwiperGestureRecognizer(new TwoFingerSwipeGestureRecognizer()) +{ +} + +bool ViewerGestureHandler::handleEvent(QEvent *event) +{ + // GESTURE HANDLING + if (event->type() == QEvent::Gesture) { + QGestureEvent *gestureEvent = static_cast(event); + // three fingers swipe, the default in Qt + if (QGesture *swipe = gestureEvent->gesture(Qt::SwipeGesture)) { + QSwipeGesture *swipeGesture = static_cast(swipe); + if (swipeGesture->state() == Qt::GestureFinished) { + qDebug() << "Qt Native swipe"; + if (swipeGesture->horizontalDirection() == QSwipeGesture::Left) { + // emit swipeLeft(); + return true; + } else if (swipeGesture->horizontalDirection() == QSwipeGesture::Right) { + // emit swipeRight(); + return true; + } + } + } + + if (QGesture *tap = gestureEvent->gesture(Qt::TapGesture)) { + QTapGesture *tapGesture = static_cast(tap); + if (tapGesture->state() == Qt::GestureFinished) { + // emit tapGestureDetected(); + qDebug() << "TAP yay"; + return true; + } + } + + if (QGesture *oneFingersSwipe = gestureEvent->gesture(oneFingerSwiperGestureRecognizerType)) { + SwipeGesture *oneFingersSwipeGesture = static_cast(oneFingersSwipe); + + if (oneFingersSwipeGesture->state() == Qt::GestureFinished) { + qDebug() << "yay 1"; + return true; + } + } + + if (QGesture *twoFingersSwipe = gestureEvent->gesture(twoFingerSwiperGestureRecognizerType)) { + SwipeGesture *twoFingersSwipeGesture = static_cast(twoFingersSwipe); + + if (twoFingersSwipeGesture->state() == Qt::GestureFinished) { + qDebug() << "yay 2"; + return true; + } + } + } + + // TOUCH HANDLING + + return false; +} + +void ViewerGestureHandler::setupGestureHandler(Viewer *widget) +{ + // widget->setAttribute(Qt::WA_AcceptTouchEvents); + widget->viewport()->setAttribute(Qt::WA_AcceptTouchEvents); + + // widget->grabGesture(Qt::SwipeGesture); + // widget->grabGesture(Qt::TapGesture); + + // oneFingerSwiperGestureRecognizerType = QGestureRecognizer::registerRecognizer(oneFingerSwiperGestureRecognizer); + twoFingerSwiperGestureRecognizerType = QGestureRecognizer::registerRecognizer(twoFingerSwiperGestureRecognizer); + + // widget->grabGesture(oneFingerSwiperGestureRecognizerType); + widget->viewport()->grabGesture(twoFingerSwiperGestureRecognizerType); +} + +//-- + +SwipeGesture::SwipeGesture(QObject *parent) + : QGesture(parent), swipeGesturePrivate(new SwipeGesturePrivate) +{ + swipeGesturePrivate->gestureType = Qt::SwipeGesture; +} + +/*! + Destructor. +*/ +SwipeGesture::~SwipeGesture() +{ +} + +SwipeGesture::SwipeDirection SwipeGesture::horizontalDirection() const +{ + auto d = swipeGesturePrivate; + if (d->swipeAngle < 0 || d->swipeAngle == 90 || d->swipeAngle == 270) + return SwipeGesture::NoDirection; + else if (d->swipeAngle < 90 || d->swipeAngle > 270) + return SwipeGesture::Right; + else + return SwipeGesture::Left; +} + +SwipeGesture::SwipeDirection SwipeGesture::verticalDirection() const +{ + auto d = swipeGesturePrivate; + if (d->swipeAngle <= 0 || d->swipeAngle == 180) + return SwipeGesture::NoDirection; + else if (d->swipeAngle < 180) + return SwipeGesture::Up; + else + return SwipeGesture::Down; +} + +qreal SwipeGesture::swipeAngle() const +{ + return swipeGesturePrivate->swipeAngle; +} + +void SwipeGesture::setSwipeAngle(qreal value) +{ + swipeGesturePrivate->swipeAngle = value; +} + +OneFingerSwipeGestureRecognizer::OneFingerSwipeGestureRecognizer() + : QGestureRecognizer() +{ +} + +QGesture *OneFingerSwipeGestureRecognizer::create(QObject *target) +{ + return new SwipeGesture(target); +} + +QGestureRecognizer::Result OneFingerSwipeGestureRecognizer::recognize(QGesture *state, + QObject *, + QEvent *event) +{ + SwipeGesture *q = static_cast(state); + SwipeGesturePrivate *d = q->swipeGesturePrivate; + + QGestureRecognizer::Result result = QGestureRecognizer::Ignore; + + switch (event->type()) { + case QEvent::TouchBegin: { + d->velocityValue = 1; + d->time.start(); + d->state = SwipeGesturePrivate::Started; + result = QGestureRecognizer::MayBeGesture; + break; + } + case QEvent::TouchEnd: { + if (q->state() != Qt::NoGesture) { + result = QGestureRecognizer::FinishGesture; + } else { + result = QGestureRecognizer::CancelGesture; + } + break; + } + case QEvent::TouchUpdate: { + const QTouchEvent *ev = static_cast(event); + if (d->state == SwipeGesturePrivate::NoGesture) + result = QGestureRecognizer::CancelGesture; + else if (ev->points().size() == 1) { + d->state = SwipeGesturePrivate::PointsReached; + const QEventPoint &p1 = ev->points().at(0); + + if (d->lastPositions[0].isNull()) { + d->lastPositions[0] = p1.globalPressPosition().toPoint(); + } + d->hotSpot = p1.globalPosition(); + d->isHotSpotSet = true; + + int xDistance = p1.globalPosition().x() - d->lastPositions[0].x(); + int yDistance = p1.globalPosition().y() - d->lastPositions[0].y(); + + const int distance = xDistance >= yDistance ? xDistance : yDistance; + int elapsedTime = d->time.restart(); + if (!elapsedTime) + elapsedTime = 1; + d->velocityValue = 0.9 * d->velocityValue + (qreal)distance / elapsedTime; + d->swipeAngle = QLineF(p1.globalPressPosition(), p1.globalPosition()).angle(); + + static const int MoveThreshold = 50; + static const int directionChangeThreshold = MoveThreshold / 8; + if (qAbs(xDistance) > MoveThreshold || qAbs(yDistance) > MoveThreshold) { + // measure the distance to check if the direction changed + d->lastPositions[0] = p1.globalPosition().toPoint(); + result = QGestureRecognizer::TriggerGesture; + // QTBUG-46195, small changes in direction should not cause the gesture to be canceled. + if (d->verticalDirection == SwipeGesture::NoDirection || qAbs(yDistance) > directionChangeThreshold) { + const SwipeGesture::SwipeDirection vertical = yDistance > 0 + ? SwipeGesture::Down + : SwipeGesture::Up; + if (d->verticalDirection != SwipeGesture::NoDirection && d->verticalDirection != vertical) + result = QGestureRecognizer::CancelGesture; + d->verticalDirection = vertical; + } + if (d->horizontalDirection == SwipeGesture::NoDirection || qAbs(xDistance) > directionChangeThreshold) { + const SwipeGesture::SwipeDirection horizontal = xDistance > 0 + ? SwipeGesture::Right + : SwipeGesture::Left; + if (d->horizontalDirection != SwipeGesture::NoDirection && d->horizontalDirection != horizontal) + result = QGestureRecognizer::CancelGesture; + d->horizontalDirection = horizontal; + } + } else { + if (q->state() != Qt::NoGesture) + result = QGestureRecognizer::TriggerGesture; + else + result = QGestureRecognizer::MayBeGesture; + } + } else if (ev->points().size() > 1) { + result = QGestureRecognizer::CancelGesture; + } else { // less than 1 touch points, so it wont happen? + switch (d->state) { + case SwipeGesturePrivate::NoGesture: + result = QGestureRecognizer::MayBeGesture; + break; + case SwipeGesturePrivate::Started: + result = QGestureRecognizer::Ignore; + break; + case SwipeGesturePrivate::PointsReached: + result = (ev->touchPointStates() & QEventPoint::State::Pressed) + ? QGestureRecognizer::CancelGesture + : QGestureRecognizer::Ignore; + break; + } + } + break; + } + default: + break; + } + return result; +} + +void OneFingerSwipeGestureRecognizer::reset(QGesture *state) +{ + SwipeGesture *q = static_cast(state); + SwipeGesturePrivate *d = q->swipeGesturePrivate; + + d->verticalDirection = d->horizontalDirection = SwipeGesture::NoDirection; + d->swipeAngle = 0; + + d->lastPositions[0] = QPoint(); + d->state = SwipeGesturePrivate::NoGesture; + d->velocityValue = 0; + d->time.invalidate(); + + QGestureRecognizer::reset(state); +} + +TwoFingerSwipeGestureRecognizer::TwoFingerSwipeGestureRecognizer() + : QGestureRecognizer() +{ +} + +QGesture *TwoFingerSwipeGestureRecognizer::create(QObject *target) +{ + return new SwipeGesture(target); +} + +QGestureRecognizer::Result TwoFingerSwipeGestureRecognizer::recognize(QGesture *state, + QObject *, + QEvent *event) +{ + // qDebug() << "TWO" << state; + SwipeGesture *q = static_cast(state); + SwipeGesturePrivate *d = q->swipeGesturePrivate; + + QGestureRecognizer::Result result = QGestureRecognizer::Ignore; + + switch (event->type()) { + case QEvent::TouchBegin: { + qDebug() << "TOUCH BEGIN"; + } + + case QEvent::TouchEnd: { + qDebug() << "TOUCH END"; + } + + case QEvent::TouchUpdate: { + qDebug() << "TOUCH UPDATE"; + } + + default: { + // qDebug() << event->type(); + break; + } + } + + return result; + + switch (event->type()) { + case QEvent::TouchBegin: { + qDebug() << "TOUCH BEGIN"; + d->velocityValue = 1; + d->time.start(); + d->state = SwipeGesturePrivate::Started; + result = QGestureRecognizer::MayBeGesture; + break; + } + case QEvent::TouchEnd: { + qDebug() << "TOUCH END"; + + if (d->state != SwipeGesturePrivate::NoGesture) { + result = QGestureRecognizer::FinishGesture; + } else { + result = QGestureRecognizer::CancelGesture; + } + + break; + } + case QEvent::TouchUpdate: { + qDebug() << "TOUCH UPDATE"; + const QTouchEvent *ev = static_cast(event); + if (d->state == SwipeGesturePrivate::NoGesture) { + qDebug() << "NoGesture"; + + d->velocityValue = 1; + d->time.start(); + d->state = SwipeGesturePrivate::Started; + + result = QGestureRecognizer::CancelGesture; + } else if (ev->points().size() == 2) { + qDebug() << "HMMMMMM"; + d->state = SwipeGesturePrivate::PointsReached; + const QEventPoint &p1 = ev->points().at(0); + const QEventPoint &p2 = ev->points().at(1); + + if (d->lastPositions[0].isNull()) { + d->lastPositions[0] = p1.globalPressPosition().toPoint(); + d->lastPositions[1] = p2.globalPressPosition().toPoint(); + } + d->hotSpot = p1.globalPosition(); + d->isHotSpotSet = true; + + int xDistance = (p1.globalPosition().x() - d->lastPositions[0].x() + + p2.globalPosition().x() - d->lastPositions[1].x()) / + 2; + int yDistance = (p1.globalPosition().y() - d->lastPositions[0].y() + + p2.globalPosition().y() - d->lastPositions[1].y()) / + 2; + + const int distance = xDistance >= yDistance ? xDistance : yDistance; + int elapsedTime = d->time.restart(); + if (!elapsedTime) + elapsedTime = 1; + d->velocityValue = 0.9 * d->velocityValue + (qreal)distance / elapsedTime; + d->swipeAngle = QLineF(p1.globalPressPosition(), p1.globalPosition()).angle(); + + static const int MoveThreshold = 50; + static const int directionChangeThreshold = MoveThreshold / 8; + if (qAbs(xDistance) > MoveThreshold || qAbs(yDistance) > MoveThreshold) { + qDebug() << "distance " << qAbs(xDistance) << " - " << qAbs(yDistance); + // measure the distance to check if the direction changed + d->lastPositions[0] = p1.globalPosition().toPoint(); + d->lastPositions[1] = p2.globalPosition().toPoint(); + result = QGestureRecognizer::TriggerGesture; + // QTBUG-46195, small changes in direction should not cause the gesture to be canceled. + if (d->verticalDirection == SwipeGesture::NoDirection || qAbs(yDistance) > directionChangeThreshold) { + const SwipeGesture::SwipeDirection vertical = yDistance > 0 + ? SwipeGesture::Down + : SwipeGesture::Up; + if (d->verticalDirection != SwipeGesture::NoDirection && d->verticalDirection != vertical) + result = QGestureRecognizer::CancelGesture; + d->verticalDirection = vertical; + } + if (d->horizontalDirection == SwipeGesture::NoDirection || qAbs(xDistance) > directionChangeThreshold) { + const SwipeGesture::SwipeDirection horizontal = xDistance > 0 + ? SwipeGesture::Right + : SwipeGesture::Left; + if (d->horizontalDirection != SwipeGesture::NoDirection && d->horizontalDirection != horizontal) + result = QGestureRecognizer::CancelGesture; + d->horizontalDirection = horizontal; + } + + result = QGestureRecognizer::MayBeGesture; + } else { + qDebug() << "not enough distance " << qAbs(xDistance) << " - " << qAbs(yDistance); + if (d->state != SwipeGesturePrivate::NoGesture) + result = QGestureRecognizer::TriggerGesture; + else + result = QGestureRecognizer::MayBeGesture; + } + } else if (ev->points().size() > 2) { + qDebug() << "Cancel gesture"; + result = QGestureRecognizer::CancelGesture; + } else { // less than 2 touch points + qDebug() << "less than 2"; + switch (d->state) { + case SwipeGesturePrivate::NoGesture: + result = QGestureRecognizer::MayBeGesture; + break; + case SwipeGesturePrivate::Started: + result = QGestureRecognizer::Ignore; + break; + case SwipeGesturePrivate::PointsReached: + result = (ev->touchPointStates() & QEventPoint::State::Pressed) + ? QGestureRecognizer::CancelGesture + : QGestureRecognizer::Ignore; + break; + } + } + break; + } + default: + break; + } + return result; +} + +void TwoFingerSwipeGestureRecognizer::reset(QGesture *state) +{ + qDebug() << "Reset"; + SwipeGesture *q = static_cast(state); + SwipeGesturePrivate *d = q->swipeGesturePrivate; + + d->verticalDirection = d->horizontalDirection = SwipeGesture::NoDirection; + d->swipeAngle = 0; + + d->lastPositions[0] = d->lastPositions[1] = QPoint(); + d->state = SwipeGesturePrivate::NoGesture; + d->velocityValue = 0; + d->time.invalidate(); + + QGestureRecognizer::reset(state); +} diff --git a/YACReader/viewer_gesture_handler.h b/YACReader/viewer_gesture_handler.h new file mode 100644 index 00000000..4325e2bd --- /dev/null +++ b/YACReader/viewer_gesture_handler.h @@ -0,0 +1,125 @@ +#ifndef VIEWERGESTUREHANDLER_H +#define VIEWERGESTUREHANDLER_H + +#include +#include + +#include "viewer.h" + +class SwipeGesturePrivate; +class SwipeGesture : public QGesture +{ + Q_OBJECT + + Q_DECLARE_PRIVATE(SwipeGesture) + + Q_PROPERTY(SwipeDirection horizontalDirection READ horizontalDirection STORED false) + Q_PROPERTY(SwipeDirection verticalDirection READ verticalDirection STORED false) + Q_PROPERTY(qreal swipeAngle READ swipeAngle WRITE setSwipeAngle) + Q_PRIVATE_PROPERTY(SwipeGesture::d_func(), qreal velocity READ velocity WRITE setVelocity) + +public: + SwipeGesturePrivate *swipeGesturePrivate; + + enum SwipeDirection { NoDirection, + Left, + Right, + Up, + Down }; + Q_ENUM(SwipeDirection) + + explicit SwipeGesture(QObject *parent = nullptr); + ~SwipeGesture(); + + SwipeDirection horizontalDirection() const; + SwipeDirection verticalDirection() const; + + qreal swipeAngle() const; + void setSwipeAngle(qreal value); + + friend class TwoFingerSwipeGestureRecognizer; + friend class OneFingerSwipeGestureRecognizer; +}; + +class GesturePrivate : public QObject +{ +public: + GesturePrivate() + : gestureType(Qt::CustomGesture), state(Qt::NoGesture), isHotSpotSet(false), gestureCancelPolicy(0) + { + } + + Qt::GestureType gestureType; + Qt::GestureState state; + QPointF hotSpot; + QPointF sceneHotSpot; + uint isHotSpotSet : 1; + uint gestureCancelPolicy : 2; +}; + +class SwipeGesturePrivate : public GesturePrivate +{ +public: + enum State { + NoGesture, + Started, + PointsReached + }; + + SwipeGesturePrivate() + : horizontalDirection(SwipeGesture::NoDirection), + verticalDirection(SwipeGesture::NoDirection), + swipeAngle(0), + state(NoGesture), + velocityValue(0) + { + } + + qreal velocity() const { return velocityValue; } + void setVelocity(qreal value) { velocityValue = value; } + + SwipeGesture::SwipeDirection horizontalDirection; + SwipeGesture::SwipeDirection verticalDirection; + qreal swipeAngle; + + QPoint lastPositions[3]; + State state; + qreal velocityValue; + QElapsedTimer time; +}; + +class OneFingerSwipeGestureRecognizer : public QGestureRecognizer +{ +public: + OneFingerSwipeGestureRecognizer(); + QGesture *create(QObject *target) override; + QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event) override; + void reset(QGesture *state) override; +}; + +class TwoFingerSwipeGestureRecognizer : public QGestureRecognizer +{ +public: + TwoFingerSwipeGestureRecognizer(); + QGesture *create(QObject *target) override; + QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event) override; + void reset(QGesture *state) override; +}; + +class ViewerGestureHandler : public QObject +{ + Q_OBJECT +public: + explicit ViewerGestureHandler(QObject *parent = nullptr); + void setupGestureHandler(Viewer *widget); + bool handleEvent(QEvent *event); +signals: + +private: + OneFingerSwipeGestureRecognizer *oneFingerSwiperGestureRecognizer; + Qt::GestureType oneFingerSwiperGestureRecognizerType; + TwoFingerSwipeGestureRecognizer *twoFingerSwiperGestureRecognizer; + Qt::GestureType twoFingerSwiperGestureRecognizerType; +}; + +#endif // VIEWERGESTUREHANDLER_H