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
Forum Updated to NodeBB v4.3 + New Features

How to add swipe control on top of GridView

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
7 Posts 2 Posters 978 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