Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Rectangle::contains() doesn't reflect radius



  • Hi,
    I have several circular "Buttons" (here: Rectangle{}s with radius of half their width, named "key1", .., "key4") that are part of a MultiPointTouchArea. I need them being able to detect being pressed simultaneously (it's gonna be a fingering trainer for brass instruments). So I detect the TouchPoints and check them for matches with my Rectangles using their item::contains() method.
    Screenshot here:

    Unfortunately, contains() returns true within the whole Rectangle's area, where it should return false in the corners (where "radius: width / 2" has clipped the Rectangle).

    Trying to set a containmentMask would bring me back to the lack of a genuinely circular item in qml.

    Any ideas? Here's my code so far:

    {
                            id: touchArea
                            property bool pressed1: point1.pressed1 || point2.pressed1 || point3.pressed1 || point4.pressed1
                            property bool pressed2: point1.pressed2 || point2.pressed2 || point3.pressed2 || point4.pressed2
                            property bool pressed3: point1.pressed3 || point2.pressed3 || point3.pressed3 || point4.pressed3
                            property bool pressed4: point1.pressed4 || point2.pressed4 || point3.pressed4 || point4.pressed4
                            anchors.fill: parent
                            touchPoints: [
                                TouchPoint {
                                    id: point1
                                    property bool pressed1: point1.pressed && key1.contains(Qt.point(point1.x-key1.x, point1.y-key1.y))
                                    property bool pressed2: point1.pressed && key2.contains(Qt.point(point1.x-key2.x, point1.y-key2.y))
                                    property bool pressed3: point1.pressed && key3.contains(Qt.point(point1.x-key3.x, point1.y-key3.y))
                                    property bool pressed4: point1.pressed && key4.contains(Qt.point(point1.x-key4.x, point1.y-key4.y))
                                },
                                TouchPoint {
                                    id: point2
                                    property bool pressed1: point2.pressed && key1.contains(Qt.point(point2.x-key1.x, point2.y-key1.y))
                                    property bool pressed2: point2.pressed && key2.contains(Qt.point(point2.x-key2.x, point2.y-key2.y))
                                    property bool pressed3: point2.pressed && key3.contains(Qt.point(point2.x-key3.x, point2.y-key3.y))
                                    property bool pressed4: point2.pressed && key4.contains(Qt.point(point2.x-key4.x, point2.y-key4.y))
                                },
                                TouchPoint {
                                    id: point3
                                    property bool pressed1: point3.pressed && key1.contains(Qt.point(point3.x-key1.x, point3.y-key1.y))
                                    property bool pressed2: point3.pressed && key2.contains(Qt.point(point3.x-key2.x, point3.y-key2.y))
                                    property bool pressed3: point3.pressed && key3.contains(Qt.point(point3.x-key3.x, point3.y-key3.y))
                                    property bool pressed4: point3.pressed && key4.contains(Qt.point(point3.x-key4.x, point3.y-key4.y))
                                },
                                TouchPoint {
                                    id: point4
                                    property bool pressed1: point4.pressed && key1.contains(Qt.point(point4.x-key1.x, point4.y-key1.y))
                                    property bool pressed2: point4.pressed && key2.contains(Qt.point(point4.x-key2.x, point4.y-key2.y))
                                    property bool pressed3: point4.pressed && key3.contains(Qt.point(point4.x-key3.x, point4.y-key3.y))
                                    property bool pressed4: point4.pressed && key4.contains(Qt.point(point4.x-key4.x, point4.y-key4.y))
                                }
    
                            ]
    
                            Row {
                                anchors.fill: parent
                                Rectangle {
                                    id: key1
                                    width: Math.min(parent.width / appwin.noOfKeys, parent.height)
                                    height: width
                                    radius: width / 2
                                    color: touchArea.pressed1 ? "red" : "yellow"
                                }
                                Rectangle {
                                    id: key2
                                    width: Math.min(parent.width / appwin.noOfKeys, parent.height)
                                    height: width
                                    radius: width / 2
                                    color: touchArea.pressed2 ? "red" : "yellow"
                                }
                                Rectangle {
                                    id: key3
                                    width: Math.min(parent.width / appwin.noOfKeys, parent.height)
                                    height: width
                                    radius: width / 2
                                    color: touchArea.pressed3 ? "red" : "yellow"
                                    containmentMask: Rectangle {
                                        anchors.fill: key3
                                        radius: width/2
                                        color: "green"
                                        
                                    }
                                }
                                Rectangle {
                                    visible: appwin.noOfKeys === 4
                                    id: key4
                                    width: Math.min(parent.width / appwin.noOfKeys, parent.height)
                                    height: width
                                    radius: width / 2
                                    color: touchArea.pressed4 ? "red" : "yellow"
                                }
                            }
                        }
    

    Kind regards,
    Sebastian!



  • Query the parent item for the child key at each relevant point. If it exists, map the point to the key, and check if the resulting point is close enough to the key's center.

    import QtQuick 2.15
    import QtQuick.Window 2.15
    
    Window {
        id: window
        width: 640
        height: 480
        visible: true
    
        MultiPointTouchArea {
            id: touchArea
            anchors.fill: parent
    
            onTouchUpdated: {
                var touched = new Set()
                for (var i in touchPoints) {
                    var item = window.contentItem.childAt(touchPoints[i].x, touchPoints[i].y);
                    if (!item)
                        return;
                    var mapped = touchArea.mapToItem(item, touchPoints[i].x, touchPoints[i].y);
                    var distance = Math.sqrt((mapped.x - item.width/2)**2 + (mapped.y - item.height/2)**2);
                    if (distance < item.width / 2)
                        touched.add(item);
                }
                for (i in window.contentItem.children) {
                    if (window.contentItem.children[i] === touchArea)
                        continue;
                    else if (touched.has(window.contentItem.children[i]))
                        window.contentItem.children[i].touched = true;
                    else
                        window.contentItem.children[i].touched = false;
                }
            }
        }
    
        Rectangle {
            property bool touched: false
    
            width: Math.min(parent.width/2, parent.height)
            height: width
            radius: width / 2
            border.width: 1
            border.color: "black"
            color: touched ? "red" : "yellow"
            anchors.verticalCenter: parent.verticalCented
            anchors.right: parent.horizontalCenter
        }
        Rectangle {
            property bool touched: false
    
            width: Math.min(parent.width/2, parent.height)
            height: width
            radius: width / 2
            border.width: 1
            border.color: "black"
            color: touched ? "red" : "yellow"
            anchors.verticalCenter: parent.verticalCented
            anchors.left: parent.horizontalCenter
        }
    }
    


  • Query the parent item for the child key at each relevant point. If it exists, map the point to the key, and check if the resulting point is close enough to the key's center.

    import QtQuick 2.15
    import QtQuick.Window 2.15
    
    Window {
        id: window
        width: 640
        height: 480
        visible: true
    
        MultiPointTouchArea {
            id: touchArea
            anchors.fill: parent
    
            onTouchUpdated: {
                var touched = new Set()
                for (var i in touchPoints) {
                    var item = window.contentItem.childAt(touchPoints[i].x, touchPoints[i].y);
                    if (!item)
                        return;
                    var mapped = touchArea.mapToItem(item, touchPoints[i].x, touchPoints[i].y);
                    var distance = Math.sqrt((mapped.x - item.width/2)**2 + (mapped.y - item.height/2)**2);
                    if (distance < item.width / 2)
                        touched.add(item);
                }
                for (i in window.contentItem.children) {
                    if (window.contentItem.children[i] === touchArea)
                        continue;
                    else if (touched.has(window.contentItem.children[i]))
                        window.contentItem.children[i].touched = true;
                    else
                        window.contentItem.children[i].touched = false;
                }
            }
        }
    
        Rectangle {
            property bool touched: false
    
            width: Math.min(parent.width/2, parent.height)
            height: width
            radius: width / 2
            border.width: 1
            border.color: "black"
            color: touched ? "red" : "yellow"
            anchors.verticalCenter: parent.verticalCented
            anchors.right: parent.horizontalCenter
        }
        Rectangle {
            property bool touched: false
    
            width: Math.min(parent.width/2, parent.height)
            height: width
            radius: width / 2
            border.width: 1
            border.color: "black"
            color: touched ? "red" : "yellow"
            anchors.verticalCenter: parent.verticalCented
            anchors.left: parent.horizontalCenter
        }
    }
    


  • What a beautifully crafted solution. I've learned so many details from it (basics like the "in"-syntax in for-loops in JS or using a set()) ! Thanks a million!

    Here's my (slightly adapted) working code:

     MultiPointTouchArea {
                            id: touchArea
                            anchors.fill: parent
                                onTouchUpdated: {
                                            var touched = new Set()
                                            for (var i in touchPoints) {
                                                var item = keyRow.childAt(touchPoints[i].x, touchPoints[i].y);
                                                if (!item) {
                                                    continue;
                                                }
                                                var mapped = touchArea.mapToItem(item, touchPoints[i].x, touchPoints[i].y);
                                                var distance = Math.sqrt((mapped.x - item.width/2)**2 + (mapped.y - item.height/2)**2);
                                                if (distance < item.width / 2) {
                                                    touched.add(item);
                                                }
                                            }
                                            for (i in keyRow.children) {
                                                if (keyRow.children[i].objectName.substr(0,3) === "key") {
                                                    if (touched.has(keyRow.children[i])) {
                                                        keyRow.children[i].touched = true;
                                                    } else {
                                                        keyRow.children[i].touched = false;
                                                    }
                                                }
                                            }
                                }
    
                            Row {
                                anchors.fill: parent
                                id: keyRow
                                Rectangle {
                                    id: key1
                                    objectName: "key1"
                                    property bool touched: false
                                    width: Math.min(parent.width / appwin.noOfKeys, parent.height)
                                    height: width
                                    radius: width / 2
                                    color: touched ? "red" : "yellow"
                                }
                                Rectangle {
                                    id: key2
                                    objectName: "key2"
                                    property bool touched: false
                                    width: Math.min(parent.width / appwin.noOfKeys, parent.height)
                                    height: width
                                    radius: width / 2
                                    color: touched ? "red" : "yellow"
                                }
                                Rectangle {
                                    id: key3
                                    objectName: "key3"
                                    property bool touched: false
                                    width: Math.min(parent.width / appwin.noOfKeys, parent.height)
                                    height: width
                                    radius: width / 2
                                    color: touched ? "red" : "yellow"
                                }
                                Rectangle {
                                    id: key4
                                    objectName: "key4"
                                    visible: appwin.noOfKeys === 4
                                    property bool touched: false
                                    width: Math.min(parent.width / appwin.noOfKeys, parent.height)
                                    height: width
                                    radius: width / 2
                                    color: touched ? "red" : "yellow"
                                }
                            }
                        }
    

    Brilliant. Cheers!!



  • Happy to hear it.

    If you expand from brass to something with more keys, a piano for example, it's probably more efficient to track the previously pressed set of keys rather than iterating through the entire set.


Log in to reply