Solved Qt QML click under the bounding box
-
I'm trying to do the following.
So far the only solution I found is to create my button from C++ and to use setMask. Is there any other way in order to achieve the same results but just using QML?
My shape button:
import QtQuick 2.0 import QtQuick.Shapes 1.14 Item { id: sideButtonID property alias mouseX: mouseArea.mouseX property alias mouseY: mouseArea.mouseY readonly property bool pressed: containsMouse && mouseArea.pressed property point topLeftOffset: Qt.point(0, 0) property point topRightOffset: Qt.point(0, 0) property point bottomRightOffset: Qt.point(0, 0) property point bottomLeftOffset: Qt.point(0, 0) property var colorPressed: "green" property var colorSelected: "red" property var colorUnselected: "darkorange" signal clicked property var coordinate: generateButtonShapeCoordinates(x,y,width,height) property point topLeft: coordinate.topLeft property point topRight: coordinate.topRight property point bottomRight: coordinate.bottomRight property point bottomLeft: coordinate.bottomLeft function generateButtonShapeCoordinates(x, y, width, height){ var topLeft = Qt.point(x-width/2 - topLeftOffset.x, y-height-topLeftOffset.y) var topRight = Qt.point(x+width/2-topRightOffset.x, y-height-topRightOffset.y) var bottomRight = Qt.point(x+width/2-bottomRightOffset.x, y-bottomRightOffset.y) var bottomLeft = Qt.point(x - width/2-bottomLeftOffset.x, y-bottomLeftOffset.y) return { topLeft : topLeft, topRight : topRight, bottomRight : bottomRight, bottomLeft : bottomLeft }; } function inside(point, polygon) { // ray-casting algorithm based on // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html var x = point[0], y = point[1]; var inside = false; for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { var xi = polygon[i][0], yi = polygon[i][1]; var xj = polygon[j][0], yj = polygon[j][1]; var intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); if (intersect) inside = !inside; } return inside; } property bool containsMouse: { var topLeft = Qt.point(0-topLeftOffset.x, 0-topLeftOffset.y) var topRigh = Qt.point(width-topRightOffset.x, 0-topRightOffset.y) var bottomRight = Qt.point(width-bottomRightOffset.x, height-bottomRightOffset.y) var bottomLeft = Qt.point(0-bottomLeftOffset.x, height-bottomLeftOffset.y) var polygon = [[topLeft.x,topLeft.y],[topRigh.x,topRigh.y],[bottomRight.x,bottomRight.y],[bottomLeft.x,bottomLeft.y]] var point = [mouseX, mouseY] return inside(point, polygon) } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: if (sideButtonID.containsMouse) sideButtonID.clicked() } Shape { antialiasing: true vendorExtensionsEnabled: true asynchronous: true anchors.centerIn: parent ShapePath { id: shapepathId strokeWidth: -1 fillColor: sideButtonID.pressed ? sideButtonID.colorPressed : (sideButtonID.containsMouse ? sideButtonID.colorSelected : sideButtonID.colorUnselected) startX: sideButtonID.topLeft.x; startY: sideButtonID.topLeft.y PathLine { x: sideButtonID.topRight.x; y: sideButtonID.topRight.y } PathLine { x: sideButtonID.bottomRight.x; y: sideButtonID.bottomRight.y } PathLine { x: sideButtonID.bottomLeft.x; y: sideButtonID.bottomLeft.y } } Component.onCompleted: { console.log("TopLeft: "+sideButtonID.topLeft) console.log("TopRight: "+sideButtonID.topRight) console.log("BottomLeft: "+sideButtonID.bottomLeft) console.log("BottomRight: "+sideButtonID.bottomRight) } } }
The usage of my button:
SideButtons { z: 100 id: sideButtonID width: 130 height: 248 anchors.verticalCenterOffset: 9 anchors.horizontalCenterOffset: -255 anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter bottomRightOffset: Qt.point(100,0) onClicked: { print("X: "+mouseX) print("Y: "+mouseY) print("clicked") } }
Some of the options that I've heard so far but there are not good in my case.
- using an image with a transparent background (sure but I'm going to have the same problem)
- creating a C++ class which will inherit QAbstractButton (I would prefer to avoid as much as I can usage of C++ in the Ui side)
- usage of a Shape in combination with MouseArea (I'm doing this already but I am encountering the bounding box problem.
Is there any other way which I don't know yet about?
-
usage of a Shape in combination with MouseArea (I'm doing this already but I am encountering the bounding box problem.
You've got it almost right. In MouseArea's event handler, do not accept events which should be propagated further to your image buttons below. Example code:
MouseArea { propagateComposedEvents: true onClicked: { if (clickIsInsideShape) { doSomeAction; } else { mouse.accepted = false; return; } } }
Docs: https://doc.qt.io/qt-5/qml-qtquick-mousearea.html#clicked-signal