在MouseArea.onEntered中,检测是否只是*MouseArea*移动到光标下的原因

In MouseArea.onEntered, detect if the cause is only that the *MouseArea* moved and came to be under the cursor

在MouseArea.onEntered中,我可以检测事件触发的原因是否只是 MouseArea 移动并位于光标下,而不是反过来?

我想到了这样做:(伪代码)

    MouseArea {
        // ...
        property bool positionDirty = false
        Connections {
            target: window
            onAfterRendering: {
                positionDirty = false;
            }
        }
        onMouseAreaPosChanged: {
            positionDirty = true;
        }
        onEntered: {
            if(positionDirty) {
                positionDirty = false;
                return;
            }
            // handle event here
        }
    }

但这假设 entered 将在 mouseAreaPosChanged 之后被解雇, 之前 window.afterRendering。我对这个假设没有信心。

此外,当 MouseArea 的 祖先 移动时,或者当 MouseArea 通过锚定 positioned/sized 时,它不起作用。

您可以查看自上次活动以来 mouseXmouseY 是否发生了变化。

property int previousMouseX = mouseX; // or use other values to init
property int previousMouseY = mouseY; // e.g., 0, x, parent.x, 
                                      // or set it from extern

onEntered() {
    if (mouseX != previousMouseX || mouseY != previousMouseY) {
       // TODO do something
        previousMouseX = mouseX;
        previousMouseY = mouseY;
    }
}

如果mouseXmouseY是相对于鼠标区域0,0你可以使用mapFromItem(null, 0, 0)来获取绝对值。

假设:

This only affects the edge case, that both, the cursor and the MouseArea are moving.
My Assumption here is, that the movement of the cursor is handled before the movement of the MouseArea. I don't have any definite proof for this. Only my test with the solution below, suggests that.


解决方案

第一个挑战是检测 MouseArea 的移动。可能是它移动了,但它自己的 xy 值没有改变,例如如果它 parent 在移动。
为了解决这个问题,我将引入两个属性 globalXglobalX。然后我使用 this answer on how to track a gobal position of an object.

中的技巧

现在我要处理两个信号:globalXChangedglobalYChanged
根据我的假设,他们在 mouseXChangedmouseYChanged 之后被解雇了。我将使用标志 isEntered 来确保,如果第一个被解雇,我只处理其中一个,将其设置为 true

我将使用 globalMouseArea 上的光标位置来确定光标是否在 MouseArea 的范围内。 这需要,当时光标不在其他MouseArea,至少我知道

-> 至此我已经成功检测到入口了


第二个挑战是检测出口。这里我们有4种情况来区分:

  1. 光标进入和离开 MouseArea 因为它在移动。
  2. 光标进入和离开MouseArea是因为MouseArea
  3. 的移动
  4. 光标进入是因为MouseArea移动,离开是因为光标移动
  5. 光标进入是因为它移动,随着MouseArea移开而离开。

第一个很容易处理。在它进入后我们处理 entered,当它离开时我们处理 exited。但是在 Mitch 提到的修复之后,我们不能再依赖它了。

所以我们不会设置hoverEnabled: true并将光标的位置映射到targetMouseArea,只要光标移动,或者targetMouseArea移动,并相应地进行操作。

import QtQuick 2.7
import QtQuick.Controls 2.0

ApplicationWindow {
    id: root
    visible: true
    width: 400; height: 450
    MouseArea {
        id: globalMouseArea
        anchors.fill: parent
        hoverEnabled: true
        onClicked: ani.restart()
    }

    Rectangle {
        x: 300
        y: 300
        width: 50
        height: 50
        color: 'green'
    }

    Rectangle {
        id: rect
        width: 50
        height: 50
        color: 'red'

        Text {
            text: targetMouseArea.isEntered.toString()
        }

        MouseArea {
            id: targetMouseArea
            anchors.fill: parent
            signal enteredBySelfMovement
            signal enteredByMouseMovement

            onEnteredByMouseMovement: console.log('Cause: Mouse')
            onEnteredBySelfMovement: console.log('Cause: Self')

            property point globalPos: {
                var c = Qt.point(0, 0)
                var itm = this
                for (; itm.parent !== null; itm = itm.parent) {
                    c.x += itm.x
                    c.y += itm.y
                }
                return c
            }
            property bool isEntered: false

            function checkCollision(sig) {
                if ((globalPos.y < globalMouseArea.mouseY)
                    && (globalPos.y + height > globalMouseArea.mouseY)
                    && (globalPos.x < globalMouseArea.mouseX)
                    && (globalPos.x + width > globalMouseArea.mouseX)) {
                    if (!isEntered) { 
                        isEntered = true
                        sig()
                    }
                }
                else if (isEntered && !containsMouse) {
                    console.log(isEntered = false)
                }
            }

            onGlobalPosChanged: {
                checkCollision(enteredBySelfMovement)
            }

            Connections {
                target: globalMouseArea
                onPositionChanged: {
                    targetMouseArea.checkCollision(targetMouseArea.enteredByMouseMovement)
                }
            }
        }
    }

    NumberAnimation {
        id: ani
        target: rect
        properties: 'x,y'
        from: 0
        to: 300
        running: true
        duration: 10000
    }
}

遗留问题:当我们在targetMouseArea内点击时,只要按下一个按钮,我们就检测不到遗留。