Get mouse x & y in nested MouseArea with hoverEnabled

200 views Asked by At

I made a simplified MapImage component which allows to zoom and pan an image with the mouse. This component uses Flickable and MouseArea components. The MapImage component just handles image display, zooming and panning. I want to use another MouseArea in the MapImage instance in main.qml (to be able to place objects using a Canvas but this is not important here). This is not the job of MapImage, so I really need this second MouseArea component.

I need to set the hoverEnabled property to true because I need onPositionChanged and others events... But this property seems to cause problems with mouseX and mouseY values taken from my updateFlickable function. When I'm zooming with the mouse wheel, zoom does not occur at the mouse position...

I've made a minimal example available here or in a gist.

Any hint to solve this?

main.qml

import QtQml.Models 2.11
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11

MapImage {
    id: map
    height: 600
    width: 800

    imageSource: "https://images.unsplash.com/photo-1651634099253-720df02a0d50"

    MouseArea {
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton
        hoverEnabled: true // this is required to use onPositionChanged
        preventStealing: false

        onPressed: {
            // needed for flickable
            mouse.accepted = false;
        }

        onPositionChanged: {
            // do something.
        }

    }
}

MapImage.qml

import QtQuick 2.11

Item {
    id: root
    property alias imageSource: image.source

    Flickable {
        id: flickable
        anchors.fill: parent
        contentWidth: props.originalImageWidth
        contentHeight: props.originalImageHeight

        Image {
            id: image
            fillMode: Image.PreserveAspectFit

            onStatusChanged: {
                if (status === Image.Ready) {
                    props.originalImageWidth = sourceSize.width;
                    props.originalImageHeight = sourceSize.height;
                    props.changeCurrentScale(1);
                }
            }

            // define the image display size
            width: flickable.contentWidth;
            height: flickable.contentHeight;

            MouseArea {
                id: mouseArea
                anchors.fill: parent
                acceptedButtons: Qt.LeftButton
                hoverEnabled: true

                onWheel: {
                    wheel.accepted = false;
                    props.changeCurrentScale(wheel.angleDelta.y);
                }
            }
        }

        QtObject {
            id: props

            // original image size
            property int originalImageWidth
            property int originalImageHeight

            property real scaleStep: 0.2
            property real currentScale: 0.1

            onCurrentScaleChanged: updateFlickable(currentScale);

            function updateFlickable(scale) {
                console.log(mouseArea.mouseX, mouseArea.mouseY); // <------ I am no longer able to get mouse x and y coordinates
                flickable.resizeContent(originalImageWidth * scale, originalImageHeight * scale, Qt.point(mouseArea.mouseX, mouseArea.mouseY));
                flickable.returnToBounds();
            }

            function changeCurrentScale(wheelDelta) {
                if (wheelDelta > 0) currentScale = currentScale * (1 + scaleStep);
                else currentScale = currentScale / (1 + scaleStep);
            }
        }
    }
}
1

There are 1 answers

0
matt On BEST ANSWER

Finally found a solution. I had to add a new property in my MapImage component. This property role is to store the updated position of the mouse in the parent mouse area in the parent coordinate system. After that, I have to use mapToItem to convert in the flickable.contentItem coordinate system.

main.qml

import QtQml.Models 2.11
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11

MapImage {
    id: map
    height: 600
    width: 800

    imageSource: "https://images.unsplash.com/photo-1651634099253-720df02a0d50"

    MouseArea {
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton
        hoverEnabled: true // this is required to use onPositionChanged
        preventStealing: false

        onPressed: {
            // needed for flickable
            mouse.accepted = false;
        }

        onPositionChanged: {
            // CHANGE HERE
            // the position must be updated on every position change
            map.parentMouseAreaPosition = Qt.point(mouse.x, mouse.y);
            // TO HERE
        }
    }
}

MapImage.qml

import QtQuick 2.11

Item {
    id: root
    property alias imageSource: image.source

    // CHANGE HERE
    // the current mouse position in the parent mouse area in parent coordinate system
    property var parentMouseAreaPosition: Qt.point(0, 0)

    // this function maps the parent coordinate system to that of contentItem
    function __mapToContentItem(x, y) {
        return mapToItem(flickable.contentItem, x, y);
    }
    // TO HERE

    Flickable {
        id: flickable
        anchors.fill: parent
        contentWidth: props.originalImageWidth
        contentHeight: props.originalImageHeight

        Image {
            id: image
            fillMode: Image.PreserveAspectFit

            onStatusChanged: {
                if (status === Image.Ready) {
                    props.originalImageWidth = sourceSize.width;
                    props.originalImageHeight = sourceSize.height;
                    props.changeCurrentScale(1);
                }
            }

            // define the image display size
            width: flickable.contentWidth;
            height: flickable.contentHeight;

            MouseArea {
                id: mouseArea
                anchors.fill: parent
                acceptedButtons: Qt.LeftButton
                hoverEnabled: true

                onWheel: {
                    wheel.accepted = false;
                    props.changeCurrentScale(wheel.angleDelta.y);
                }
            }
        }

        QtObject {
            id: props

            // original image size
            property int originalImageWidth
            property int originalImageHeight

            property real scaleStep: 0.2
            property real currentScale: 0.1

            onCurrentScaleChanged: updateFlickable(currentScale);

            function updateFlickable(scale) {
                // CHANGE HERE
                // get the mapped point
                let point = __mapToContentItem(root.parentMouseAreaPosition.x, root.parentMouseAreaPosition.y);
                console.log(point.x, point.y);
                flickable.resizeContent(originalImageWidth * scale, originalImageHeight * scale, point);
                // TO HERE
                flickable.returnToBounds();
            }

            function changeCurrentScale(wheelDelta) {
                if (wheelDelta > 0) currentScale = currentScale * (1 + scaleStep);
                else currentScale = currentScale / (1 + scaleStep);
            }
        }
    }
}

I don't know if there is a better solution.