Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change cursor shape in qml when MouseArea is covered with another MouseArea

There are two MouseAreas with two different responsibilities and one (green) is positioned partially on top of the other (red). I would like to change cursor's shape whenever red MA is hovered (even if it is under the green MA), and I would like green MA to react to press and nothing else.

Two MA's could be in different files so I don't want to make explicit dependencies between them like setting proper cursorShape in green whenever containsMouse in red changes. Is there a way to prevent the green MouseArea from handling cursor shape?

import QtQuick 2.4
import QtQuick.Window 2.2

Window {
    visible: true
    width: 200
    height: 200

    Rectangle { anchors.fill: parent; color: "yellow"; }

    MouseArea {
        width: 150
        height: 150
        hoverEnabled: true
        cursorShape: Qt.OpenHandCursor
        Rectangle { anchors.fill: parent; color: "red"; }
        onPositionChanged: console.log("position", mouse.x, mouse.y)
        onContainsMouseChanged: console.log("containsMouse", containsMouse)
    }
    MouseArea {
        x: 50
        y: 50
        width: 150
        height: 150
        hoverEnabled: false
        Rectangle { anchors.fill: parent; color: "green"; }
        onPressed: console.log("Ahoj!")
    }
}
like image 596
Krzysztof Piekutowski Avatar asked Jan 07 '15 13:01

Krzysztof Piekutowski


2 Answers

There is no way to do this by MouseArea properties or any other ready solution. MouseArea always sets some cursor shape -- if cursorShape property is not specified than the default value is used (Qt.ArrowCursor) You can of course use mapToItem()/mapFromItem() to workaround this problem (as Mitch suggested). But there are other possibilities too:

You can temporary change visible to false of overlaying mouse area.
Alternatively if both MouseArea are siblings, you can operate on z property to obtain specific hierarchy of object suitable for your needs.

like image 73
pawel Avatar answered Sep 19 '22 04:09

pawel


I don't think it's possible to do that, at least from within QML. The green mouse area doesn't have hoverEnabled set to true, so you won't receive any position changes for it.

A better way of approaching this problem is to use a larger MouseArea that fills the maximum area you're interested in, and use mapToItem() / mapFromItem() to translate the global mouse position to local coordinates for comparison against each mouse area:

import QtQuick 2.4
import QtQuick.Window 2.2

Window {
    id: window
    visible: true
    width: 200
    height: 200

    Rectangle {
        anchors.fill: parent
        color: "yellow"
    }

    MouseArea {
        id: globalMouseArea
        anchors.fill: parent
        hoverEnabled: true
    }

    MouseArea {
        id: redMouseArea
        width: 150
        height: 150
        cursorShape: containsMouse ? Qt.OpenHandCursor : Qt.ArrowCursor
        enabled: false

        readonly property bool containsMouse: {
            var relativePos = mapFromItem(globalMouseArea, globalMouseArea.mouseX, globalMouseArea.mouseY);
            return contains(Qt.point(relativePos.x, relativePos.y));
        }

        Rectangle {
            anchors.fill: parent
            color: "red"
        }
    }
    Rectangle {
        id: greenMouseArea
        x: 50
        y: 50
        width: 150
        height: 150
        color: containsMouse ? "brown" : "green"

        readonly property bool containsMouse: {
            var relativePos = mapFromItem(globalMouseArea, globalMouseArea.mouseX, globalMouseArea.mouseY);
            return contains(Qt.point(relativePos.x, relativePos.y));
        }

        Connections {
            target: globalMouseArea
            onPressed: if (greenMouseArea.containsMouse) greenMouseArea.pressed()
        }

        signal pressed
        onPressed: console.log("Ahoj!")
    }
}

As you can see, the green mouse area is no longer a mouse area. It seems that a mouse area that has a higher stacking order than another mouse area will block position changes for the lower mouse area, even if the higher one doesn't have hoverEnabled set to true.

Also, note that it would be slightly more concise if it weren't for QTBUG-41452. Namely, you could shorten the containsMouse expression:

readonly property bool containsMouse: contains(mapFromItem(globalMouseArea, globalMouseArea.mouseX, globalMouseArea.mouseY))

If you're concerned about the code duplication here, then you can use a function instead:

import QtQuick 2.4
import QtQuick.Window 2.2

Window {
    id: window
    visible: true
    width: 200
    height: 200

    Rectangle {
        anchors.fill: parent
        color: "yellow"
    }

    MouseArea {
        id: globalMouseArea
        anchors.fill: parent
        hoverEnabled: true
    }

    function containsMouse(item) {
        var relativePos = globalMouseArea.mapToItem(item, globalMouseArea.mouseX, globalMouseArea.mouseY);
        return item.contains(Qt.point(relativePos.x, relativePos.y));
    }

    MouseArea {
        id: redMouseArea
        width: 150
        height: 150
        cursorShape: window.containsMouse(redMouseArea) ? Qt.OpenHandCursor : Qt.ArrowCursor
        enabled: false

        Rectangle {
            anchors.fill: parent
            color: "red"
        }
    }
    Rectangle {
        id: greenMouseArea
        x: 50
        y: 50
        width: 150
        height: 150
        color: containsMouse ? "brown" : "green"

        readonly property bool containsMouse: window.containsMouse(greenMouseArea)

        Connections {
            target: globalMouseArea
            onPressed: if (greenMouseArea.containsMouse) greenMouseArea.pressed()
        }

        signal pressed
        onPressed: console.log("Ahoj!")
    }
}

I used a property for the green mouse area, as it would otherwise call containsMouse() twice, which is wasteful.

like image 28
Mitch Avatar answered Sep 21 '22 04:09

Mitch