Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. How to add swipe control on top of GridView
Qt 6.11 is out! See what's new in the release blog

How to add swipe control on top of GridView

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
7 Posts 2 Posters 1.4k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • G Offline
    G Offline
    gomgom
    wrote on last edited by
    #1

    I have a StackView, which loads different GridView's. When a new GridView is pushed, the default animation slides the new page from the left to the right. The natural gesture to pop would hence be a right swipe. However I cannot find how to do that.

    Some threads show ho to "manually" analyze a mouse area to detect a swipe, but it seems hard to believe that Qt won't let us implement a simple swipe gesture.

    Others mention using a SwipeView. But I want to keep the StackView/GridView combination bahaviour (the use case is more complex, with a collection of button navigating down a menu tree). Users can only swipe to the right, to go back one level.

    Here is a simplified code:

    StackView {
      id: testStack
      anchors.fill: parent
      Component.onCompleted: {
        push(testGridView);
      }
    
      Component {
        id: testGridView
        Rectangle {
          GridView {
            model:testModel
            anchors.fill: parent
            cellWidth: width / 2
            delegate: Rectangle {
              color: modelColor
              width: testStack.width * .4
              height: width
              MouseArea {
                anchors.fill: parent
                onClicked: {
                  if (add)
                    testStack.push(testGridView);
                  else
                    testStack.pop();
                }
              }
            }
          }
        }
      }
    
      MouseArea {
        id: testMouseArea
        anchors.fill: parent
        propagateComposedEvents: true
        onClicked: {
          mouse.accepted = false
        }
        // Ideal case:
        // onRightSwipe() {
        //   testStack.pop() }
      }
    }
    
    ListModel {
      id: testModel
      ListElement {modelColor: "green"; add: true}
      ListElement {modelColor: "red"; add: false}
    }
    

    At least my MouseArea doesn't mess the GridView up/down swipes, and clicks. But it doesn't handle swipes.

    I've tried to add other elements instead of the MouseArea, but these typically don't have a propagateComposedEvents equivalebt, and will capture all interactions.

    How to handle this? Do I really have to re-do from scratch what a SwipeView does just fine? Thanks!

    1 Reply Last reply
    0
    • G Offline
      G Offline
      gomgom
      wrote on last edited by
      #2

      I am starting to wonder if this is possible at all. I created a new object type, based on QQuickItem, and layed it on top of my StackView.
      Unfortunately it looks like only one object can grab the mouse events at a given time.
      Am I getting this right? Is there a way for my object to analyze the events, but pass them on to the underlying objects?

      T 1 Reply Last reply
      0
      • G gomgom

        I am starting to wonder if this is possible at all. I created a new object type, based on QQuickItem, and layed it on top of my StackView.
        Unfortunately it looks like only one object can grab the mouse events at a given time.
        Am I getting this right? Is there a way for my object to analyze the events, but pass them on to the underlying objects?

        T Offline
        T Offline
        Tom_H
        wrote on last edited by
        #3

        @gomgom You can propagate the mouse events by emitting a signal from your signal handler.

        G 1 Reply Last reply
        0
        • T Tom_H

          @gomgom You can propagate the mouse events by emitting a signal from your signal handler.

          G Offline
          G Offline
          gomgom
          wrote on last edited by
          #4

          @tom_h Thanks for the answer, do you know what would be the receiving slot?

          T 1 Reply Last reply
          0
          • G gomgom

            @tom_h Thanks for the answer, do you know what would be the receiving slot?

            T Offline
            T Offline
            Tom_H
            wrote on last edited by
            #5

            @gomgom You just call the signal as a function. e.g.

            MouseArea {
              onClicked: {
                targetMouseArea.clicked()
              }
            }
            

            I would be interested to see what you implemented in your custom QQuickItem. Did you get swipe to work with it? If not, I think you'll have to roll your own swipe gesture in your MouseArea. Not hard, but I agree with you -- Qt should include a built in swipe signal.

            G 1 Reply Last reply
            0
            • T Tom_H

              @gomgom You just call the signal as a function. e.g.

              MouseArea {
                onClicked: {
                  targetMouseArea.clicked()
                }
              }
              

              I would be interested to see what you implemented in your custom QQuickItem. Did you get swipe to work with it? If not, I think you'll have to roll your own swipe gesture in your MouseArea. Not hard, but I agree with you -- Qt should include a built in swipe signal.

              G Offline
              G Offline
              gomgom
              wrote on last edited by
              #6

              @tom_h Thanks for the suggestion. I don't really see how to do this in C++ though, as the different calls (mouseMoveEvent etc...) are protected. I want to do this on the C++ side, to have access to a timer.

              I am almost there though, I did end up doing my own swipe detection, looking at qquickflickable.cpp

              I think that the only way to get access to all mouse events without affecting children elements is to implement a filter, which is what I've done. The following works well under Windows.

              FlickableMouseArea.h :

              #ifndef FLICKABLEMOUSEAREA_H
              #define FLICKABLEMOUSEAREA_H
              
              #include <QQuickItem>
              #include <QElapsedTimer>
              #include <QMouseEvent>
              
              #define MAX_FLICK_ANGLE_RATIO 0.3
              #define MAX_FLICK_TIME_MS 150
              #define MIN_FLICK_LENGTH_INCH 0.4
              
              class FlickableMouseArea : public QQuickItem
              {
                Q_OBJECT
              public:
                FlickableMouseArea();
                Q_PROPERTY(int physicalDpi READ physicalDpi WRITE setPhysicalDpi NOTIFY physicalDpiChanged)
                int physicalDpi() {return _physicalDpi;}
                void setPhysicalDpi(int newPhysicalDpi);
              
              signals:
                //void PropagateMouseToChanged();
                void leftFlick();
                void rightFlick();
                void log(QString message);
                void physicalDpiChanged();
              
              protected:
                bool childMouseEventFilter(QQuickItem *item, QEvent *event) override;
                void handleMousePressEvent(QMouseEvent *event);
                bool handleMouseMoveEvent(QMouseEvent *event);
                bool handleMouseReleaseEvent(QMouseEvent *event);
              
              private:
                QPoint _startingPosition;
                QElapsedTimer _timer;
                int _physicalDpi;
              
                bool _detectFlick(QMouseEvent *event, const bool notify);
                void _filterSlowOrAngled(QMouseEvent *event);
              };
              
              #endif // FLICKABLEMOUSEAREA_H
              
              

              FlickableMouseArea.cpp:

              #include "flickablemousearea.h"
              
              FlickableMouseArea::FlickableMouseArea():
                _physicalDpi(157)
              {
                setAcceptedMouseButtons(Qt::LeftButton);
                setFiltersChildMouseEvents(true);
                setAcceptTouchEvents(false); // rely on mouse events synthesized from touch
              }
              
              
              void FlickableMouseArea::setPhysicalDpi(int newPhysicalDpi)
              {
                if (_physicalDpi != newPhysicalDpi) {
                  _physicalDpi = newPhysicalDpi;
                  emit physicalDpiChanged();
                  log("*** New physical DPI: " + QString(_physicalDpi)
                      + ", flick detection over " + QString(int(MIN_FLICK_LENGTH_INCH * _physicalDpi)) + "");
                }
              }
              
              
              bool FlickableMouseArea::childMouseEventFilter(QQuickItem *item, QEvent *event)
              {
                Q_UNUSED(item)
                QMouseEvent* mouseEvent;
                switch (event->type()) {
                case QEvent::MouseButtonPress:
                  mouseEvent = static_cast<QMouseEvent *>(event);
                  handleMousePressEvent(mouseEvent);
                  break;
                case QEvent::MouseMove:
                  mouseEvent = static_cast<QMouseEvent *>(event);
                  return handleMouseMoveEvent(mouseEvent);
                case QEvent::MouseButtonRelease:
                  mouseEvent = static_cast<QMouseEvent *>(event);
                  return handleMouseReleaseEvent(mouseEvent);
                default:
                  break;
                }
                return false;
              }
              
              
              void FlickableMouseArea::handleMousePressEvent(QMouseEvent *event) {
                _timer.start();
                _startingPosition = event->pos();
              }
              
              
              bool FlickableMouseArea::handleMouseMoveEvent(QMouseEvent *event) {
                _filterSlowOrAngled(event);
                return _detectFlick(event, false);
              }
              
              
              bool FlickableMouseArea::handleMouseReleaseEvent(QMouseEvent *event) {
                _filterSlowOrAngled(event);
                bool detected = _detectFlick(event, true);
                _timer.invalidate();
                return detected;
              }
              
              
              void FlickableMouseArea::_filterSlowOrAngled(QMouseEvent *event) {
                if (_timer.isValid()) {
                  if (_timer.elapsed()) {
                    // Don't analyze 2 events too close in time
                    int xDelta = event->pos().x() - _startingPosition.x();
                    int yDelta = event->pos().y() - _startingPosition.y();
              
                    if (abs(yDelta) > (MAX_FLICK_ANGLE_RATIO * abs(xDelta))) {
                      log("*** FlickableMouseArea - move too angled - invalidate detection ***");
                      _timer.invalidate();
                    }
                    else {
                      if (_timer.elapsed() > MAX_FLICK_TIME_MS) {
                        // Moving too slowly
                        log("*** FlickableMouseArea - move too slow - invalidate detection ***");
                        _timer.invalidate();
                      }
                    }
                  }
                }
              
                if (!_timer.isValid()) {
                  // Restarts detection after timer becomes invalid
                  _timer.start();
                  _startingPosition = event->pos();
                }
              }
              
              
              bool FlickableMouseArea::_detectFlick(QMouseEvent *event, const bool notify) {
                if (_timer.isValid()) {
                  if (_timer.elapsed()) {
                    // Don't analyze 2 events too close in time
                    int xDelta = event->pos().x() - _startingPosition.x();
                    int yDelta = event->pos().y() - _startingPosition.y();
              
                    if (abs(xDelta) > (int(MIN_FLICK_LENGTH_INCH * _physicalDpi))) {
                      log("*** FlickableMouseArea flick delta: x="
                          + QString::number(xDelta)
                          + " y=" + QString::number(yDelta)
                          + ", start x=" + QString::number(_startingPosition.x())
                          + ", y=" + QString::number(_startingPosition.y())
                          + ", t=" + QString::number(_timer.elapsed())
                          + "ms, edge=" + QString::number(int(MIN_FLICK_LENGTH_INCH * _physicalDpi))
                          + ""
                          + ((notify)? " - final" : " - ongoing")
                          );
                      if (notify) {
                        if (xDelta > 0)
                          emit rightFlick();
                        else
                          emit leftFlick();
                      }
                      // This qualifies as a flick, filter it
                      return true;
                    }
                  }
                }
                return false;
              }
              
              

              But... on Android, it looks like I never get the MouseButtonRelease event. Still need to dig to see what is going on!

              G 1 Reply Last reply
              0
              • G gomgom

                @tom_h Thanks for the suggestion. I don't really see how to do this in C++ though, as the different calls (mouseMoveEvent etc...) are protected. I want to do this on the C++ side, to have access to a timer.

                I am almost there though, I did end up doing my own swipe detection, looking at qquickflickable.cpp

                I think that the only way to get access to all mouse events without affecting children elements is to implement a filter, which is what I've done. The following works well under Windows.

                FlickableMouseArea.h :

                #ifndef FLICKABLEMOUSEAREA_H
                #define FLICKABLEMOUSEAREA_H
                
                #include <QQuickItem>
                #include <QElapsedTimer>
                #include <QMouseEvent>
                
                #define MAX_FLICK_ANGLE_RATIO 0.3
                #define MAX_FLICK_TIME_MS 150
                #define MIN_FLICK_LENGTH_INCH 0.4
                
                class FlickableMouseArea : public QQuickItem
                {
                  Q_OBJECT
                public:
                  FlickableMouseArea();
                  Q_PROPERTY(int physicalDpi READ physicalDpi WRITE setPhysicalDpi NOTIFY physicalDpiChanged)
                  int physicalDpi() {return _physicalDpi;}
                  void setPhysicalDpi(int newPhysicalDpi);
                
                signals:
                  //void PropagateMouseToChanged();
                  void leftFlick();
                  void rightFlick();
                  void log(QString message);
                  void physicalDpiChanged();
                
                protected:
                  bool childMouseEventFilter(QQuickItem *item, QEvent *event) override;
                  void handleMousePressEvent(QMouseEvent *event);
                  bool handleMouseMoveEvent(QMouseEvent *event);
                  bool handleMouseReleaseEvent(QMouseEvent *event);
                
                private:
                  QPoint _startingPosition;
                  QElapsedTimer _timer;
                  int _physicalDpi;
                
                  bool _detectFlick(QMouseEvent *event, const bool notify);
                  void _filterSlowOrAngled(QMouseEvent *event);
                };
                
                #endif // FLICKABLEMOUSEAREA_H
                
                

                FlickableMouseArea.cpp:

                #include "flickablemousearea.h"
                
                FlickableMouseArea::FlickableMouseArea():
                  _physicalDpi(157)
                {
                  setAcceptedMouseButtons(Qt::LeftButton);
                  setFiltersChildMouseEvents(true);
                  setAcceptTouchEvents(false); // rely on mouse events synthesized from touch
                }
                
                
                void FlickableMouseArea::setPhysicalDpi(int newPhysicalDpi)
                {
                  if (_physicalDpi != newPhysicalDpi) {
                    _physicalDpi = newPhysicalDpi;
                    emit physicalDpiChanged();
                    log("*** New physical DPI: " + QString(_physicalDpi)
                        + ", flick detection over " + QString(int(MIN_FLICK_LENGTH_INCH * _physicalDpi)) + "");
                  }
                }
                
                
                bool FlickableMouseArea::childMouseEventFilter(QQuickItem *item, QEvent *event)
                {
                  Q_UNUSED(item)
                  QMouseEvent* mouseEvent;
                  switch (event->type()) {
                  case QEvent::MouseButtonPress:
                    mouseEvent = static_cast<QMouseEvent *>(event);
                    handleMousePressEvent(mouseEvent);
                    break;
                  case QEvent::MouseMove:
                    mouseEvent = static_cast<QMouseEvent *>(event);
                    return handleMouseMoveEvent(mouseEvent);
                  case QEvent::MouseButtonRelease:
                    mouseEvent = static_cast<QMouseEvent *>(event);
                    return handleMouseReleaseEvent(mouseEvent);
                  default:
                    break;
                  }
                  return false;
                }
                
                
                void FlickableMouseArea::handleMousePressEvent(QMouseEvent *event) {
                  _timer.start();
                  _startingPosition = event->pos();
                }
                
                
                bool FlickableMouseArea::handleMouseMoveEvent(QMouseEvent *event) {
                  _filterSlowOrAngled(event);
                  return _detectFlick(event, false);
                }
                
                
                bool FlickableMouseArea::handleMouseReleaseEvent(QMouseEvent *event) {
                  _filterSlowOrAngled(event);
                  bool detected = _detectFlick(event, true);
                  _timer.invalidate();
                  return detected;
                }
                
                
                void FlickableMouseArea::_filterSlowOrAngled(QMouseEvent *event) {
                  if (_timer.isValid()) {
                    if (_timer.elapsed()) {
                      // Don't analyze 2 events too close in time
                      int xDelta = event->pos().x() - _startingPosition.x();
                      int yDelta = event->pos().y() - _startingPosition.y();
                
                      if (abs(yDelta) > (MAX_FLICK_ANGLE_RATIO * abs(xDelta))) {
                        log("*** FlickableMouseArea - move too angled - invalidate detection ***");
                        _timer.invalidate();
                      }
                      else {
                        if (_timer.elapsed() > MAX_FLICK_TIME_MS) {
                          // Moving too slowly
                          log("*** FlickableMouseArea - move too slow - invalidate detection ***");
                          _timer.invalidate();
                        }
                      }
                    }
                  }
                
                  if (!_timer.isValid()) {
                    // Restarts detection after timer becomes invalid
                    _timer.start();
                    _startingPosition = event->pos();
                  }
                }
                
                
                bool FlickableMouseArea::_detectFlick(QMouseEvent *event, const bool notify) {
                  if (_timer.isValid()) {
                    if (_timer.elapsed()) {
                      // Don't analyze 2 events too close in time
                      int xDelta = event->pos().x() - _startingPosition.x();
                      int yDelta = event->pos().y() - _startingPosition.y();
                
                      if (abs(xDelta) > (int(MIN_FLICK_LENGTH_INCH * _physicalDpi))) {
                        log("*** FlickableMouseArea flick delta: x="
                            + QString::number(xDelta)
                            + " y=" + QString::number(yDelta)
                            + ", start x=" + QString::number(_startingPosition.x())
                            + ", y=" + QString::number(_startingPosition.y())
                            + ", t=" + QString::number(_timer.elapsed())
                            + "ms, edge=" + QString::number(int(MIN_FLICK_LENGTH_INCH * _physicalDpi))
                            + ""
                            + ((notify)? " - final" : " - ongoing")
                            );
                        if (notify) {
                          if (xDelta > 0)
                            emit rightFlick();
                          else
                            emit leftFlick();
                        }
                        // This qualifies as a flick, filter it
                        return true;
                      }
                    }
                  }
                  return false;
                }
                
                

                But... on Android, it looks like I never get the MouseButtonRelease event. Still need to dig to see what is going on!

                G Offline
                G Offline
                gomgom
                wrote on last edited by
                #7

                By the way, this item is meant to encapsulate the gridView (or Flickable, or MouseArea) for which we want to snoop the mouse's moves, and add left/right flicks...

                1 Reply Last reply
                0

                • Login

                • Login or register to search.
                • First post
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • Users
                • Groups
                • Search
                • Get Qt Extensions
                • Unsolved