3
votes

This issue happens on Windows, but not on Linux. I haven't tried any other platforms.

I have a custom class (code below) that uses QCursor to set the mouse position.

The issue is with the following code (repo):

import QtQuick 2.15
import QtQuick.Window 2.15

// Custom C++ class, implementation below
import io.github.myProject.utilities.mousehelper 1.0

Window {
    visible: true
    width: 800
    height: 600

    MouseHelper { id: mouseHelper }

    MouseArea {
        id: mouseArea
        hoverEnabled: true
        anchors.fill: parent
        property var p

        onPressed: {
            p = mouseArea.mapToGlobal(
                mouseArea.width * 0.5, mouseArea.height * 0.5);
            mouseHelper.setCursorPosition(0, 0);
        }

        onReleased: {
            mouseHelper.setCursorPosition(p.x, p.y);
        }

        onExited: {
            console.log('This should happen twice, but it only happens once.');
        }
    }
}

Steps to reproduce the issue:

  1. Mouse down on the window. The cursor will move to the top-left of the screen, and onExited will fire.
  2. Release the mouse button. The cursor will jump to the middle of the window.
  3. Move the mouse out of the window.

onExited should fire a second time when the user moves the mouse out of the window, but it doesn't. Is there some way I can either

  1. cause it to fire, or
  2. otherwise detect that the mouse has moved out of the mouse area?

onPositionChanged still fires, but I can only use this to detect when the mouse is close to the edge of the MouseArea, not when it has left.

I tried overlaying a global MouseArea on top and passing all events through as a way to do some manual special-case position checking, but I couldn't pass hover events through.


The class for setting the mouse position:

#ifndef MOUSEHELPER_H
#define MOUSEHELPER_H

#include <QObject>
#include <QCursor>

class MouseHelper : public QObject {
    Q_OBJECT
public:
    explicit MouseHelper(QObject *parent = nullptr);

    Q_INVOKABLE void setCursorPosition(int x, int y);

signals:

public slots:
};

#endif // MOUSEHELPER_H
#include "mousehelper.h"
#include <QGuiApplication>

MouseHelper::MouseHelper(QObject *parent) : QObject(parent) {}

void MouseHelper::setCursorPosition(int x, int y) {
    QCursor::setPos(x, y);
}

I register this class as a type with QML in my main function:

int main(int argc, char *argv[]) {
    // ...
    qmlRegisterType<MouseHelper>("io.github.myProject.utilities.mousehelper",
                                 1, 0, "MouseHelper");
}

I can then import it in QML and use it.

1
The code you are showing appears to be programmatically entering area2, not exiting. I must be missing something. - JarMan
@JarMan Sorry! It is programmatically entering area2. The issue is, I have no way of detecting when the user moves the mouse out of area2, because the onExited event only fires if the user moved the mouse into area2 like normal - I guess moving it programmatically doesn't "count", and so it doesn't know to track the onExited?. - Joshua Wade
@JoshuaWade Yes no problem on linux, I tried in both linux and windows only occurs on windows. I ll check more about it. Also onEntered not working after click - Yunus Temurlenk
After digging around this seems like a bug. containsMouse also returning false after released event . You can find a tricky as answered below to solve this issue maybe. - Yunus Temurlenk
I'm unable to reproduce this problem on Windows using Qt 5.12.4. So it seems to be a regression bug. Have you considered creating a bug report? - m7913d

1 Answers

5
votes

As a workaround for your problem you can use a Timer to reset the position of the mouse cursor.

Either in QML:

MouseArea {
...
    Timer {
        id: timer
        interval: 10
        repeat: false
        onTriggered: {
            mouseHelper.setCursorPosition(mouseArea.p.x, mouseArea.p.y)
        }
    }
    
    onReleased: {
        timer.start()
    }
...
}

Or in your MouseHelper class:

#include <QTimer>
...
void MouseHelper::setCursorPosition(int x, int y) {
    QTimer::singleShot(10, this, [x, y]() { QCursor::setPos(x, y); });
}

This works for me if the interval of the timer is not too small.