ListView as a radio button with radius on the border



  • Hello,

    I'm trying to make some kind of radio button using a list view and the current item to define the choice.

    The idea was to have an horizontal bar with this look :
    0_1553675275242_614cc997-ce5f-4338-9aa6-cde35cf99f02-image.png
    Here the Option 2 is selected.

    It works as I would like except for one point... The Radius on the side.
    I don't know how to clip the delegate and Highlight in the parent item.
    Does anybody know how to achieve this ?

    Here is my component :

    Rectangle {
        width: 200
        height: 50
    
        property var choix : [] //Set data here to have the list of options
    
        id: out
        color: "dimgrey"
        radius: height/2
    
        ListView{
            id : list
            anchors.fill: parent
            model: choix
            clip: true
            orientation: ListView.Horizontal
            delegate: radioDelegate
            highlight: highlightComponent
            highlightFollowsCurrentItem: false
            interactive: false
            onCurrentIndexChanged: {
                console.log("Changed : " + currentIndex)
            }
        }
    
        Component {
            id: highlightComponent
    
            Rectangle{
                anchors.fill: list.currentItem
                color: "red"
                clip : true
                anchors.centerIn: list.currentItem
                y: list.currentItem.y
                x: list.currentItem.x
            }
        }
    
    
        Component{
            id: radioDelegate
    
            Rectangle{
                id: inner
                color: "transparent"
                height: parent.height
                width: out.width/choix.length
                border.color: "black"
                clip: true
                border.width: 1
    
                Text {
                    id: text
                    text: choix[index]
                    anchors.centerIn: parent
                    font.pixelSize: 15
                }
    
                MouseArea{
                    anchors.fill: parent
                    onClicked: list.currentIndex = index
                }
            }
        }
    }
    
    

    I call it with the following lines :

            RadioList{
                anchors.centerIn: parent
                width: 250
                height: 50
                choix: ["Option1","Option2","Option3"]
            }
    

    Here is the result I do have when I select an item located on the side :
    0_1553675481868_82a921be-ae5f-46d4-9c53-a14969fcfedb-image.png
    0_1553675493640_f308f2c1-1987-4e86-ba08-5d1dc7b3f775-image.png
    I want the left and right side to keep the radius when selected.



  • Hi @DavidM29 , you can acheive that using Opacity mask.

    Here is the sample code:-

     Rectangle {
        id: masking
    
        height: buttonGridLayout.height
        width: buttonGridLayout.width
        radius: 50
        visible: false
    }
    
    OpacityMask {
        height: buttonGridLayout.height
        width: buttonGridLayout.width
        source: buttonGridLayout
        maskSource: masking
    }
    
    RowLayout {
        id: buttonGridLayout
        anchors.fill: parent
        spacing: 0
        opacity: 0
    
        Repeater {
            model: 3
    
            Rectangle {
                id: rootRect
    
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: activeFocus ? "black" : "red"
    
                Text {
                    text: index
                    anchors.centerIn: parent
                    color: "yellow"
                }
    
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        rootRect.forceActiveFocus()
                    }
                }
            }
        }
    }


  • Hi @Shrinidhi-Upadhyaya
    You should think that I'm annoying but unfortunatly I do have a limitation on the target device that makes any mask not working. I tried to use it to create a round gauge a few weeks ago and the device can't handle this kind masking. For some reasons it use a specific software that is bugged with my target device.

    Do you have any other ideas ?


  • Moderators

    @DavidM29 than I would suggest remove the highlight part all together, and change your delegate with this:

    Rectangle{
                id: inner
                color: list.currentIndex == index ? "red" :"transparent"
                ....
    }
    

    I think index is an automatic assigned property.



  • Hi @DavidM29 , ohhh that's bad. Are there only 3 buttons or is it dynamic?



  • @J.Hilk
    The issue is that the delegate is a Rectangle with no radius. The Radius in on the Rectangle that contains the ListView. If I do like you suggest the problem will be the same I believe.

    What I did is that :
    I do have a Rectangle with radius like that :
    0_1553679498796_794c55ea-4353-400b-9eef-6dd67d11eb00-image.png
    In that rectangle I put the ListView made of transparent rectangle without radius.
    Which means that if I put a color on that it will result in the same behaviour.

    @Shrinidhi-Upadhyaya
    It is Dynamic it will be between 2 to 4 buttons I think.


  • Moderators

    @DavidM29 oh in that case,
    inside the "FrameRectangle" set the clip property to true. That is more resource heavy but will do the trick



  • @J.Hilk
    It seems to be the same even with the

    clip:true
    

    It set that property to that rectangle :
    0_1553679916769_9a2ccebe-e1f0-4868-af3d-303201e35289-image.png

    But I still have the same result.

    Here is my complete component :

    import QtQuick 2.0
    
    Rectangle {
        width: 200
        height: 50
    
        property var choix : []
    
        id: out
        color: "dimgrey"
        radius: height/2
        clip: true
    
        ListView{
            id : list
            anchors.fill: parent
            model: choix
            orientation: ListView.Horizontal
            delegate: radioDelegate
            highlight: highlightComponent
            highlightFollowsCurrentItem: false
            interactive: false
        }
    
        Component {
            id: highlightComponent
    
            Rectangle{
                anchors.fill: list.currentItem
                color: "red"
                anchors.centerIn: list.currentItem
                y: list.currentItem.y
                x: list.currentItem.x
            }
        }
    
    
        Component{
            id: radioDelegate
    
            Rectangle{
                property bool selected : false
    
                id: inner
                color: "transparent"
                height: parent.height
                width: out.width/choix.length
                border.color: "black"
                border.width: 1
    
                Text {
                    id: text
                    text: choix[index]
                    anchors.centerIn: parent
                    font.pixelSize: 15
                }
    
                MouseArea{
                    anchors.fill: parent
                    onClicked: list.currentIndex = index
                }
            }
        }
    }
    
    


  • Hi @DavidM29 ,

    here is the sample code,try to run it and tell me whether it works fine or not and then we can go for optimization part.

      RowLayout {
            id: buttonGridLayout
            anchors.fill: parent
            spacing: 5
    
            Repeater {
                id: buttonRepeater
                model: 4
    
                Item {
                    id: rootRect
                    Layout.fillHeight: true
                    Layout.fillWidth: true
    
                    Item {
                        id: option1
                        anchors.fill: parent
                        visible: index === 0
                        z: visible ? 1 : 0
    
                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                option1.forceActiveFocus()
                            }
                        }
    
                        Rectangle {
                            id: rect1
                            height: parent.height
                            width: parent.width
                            color: option1.activeFocus ? "red" : "black"
                            radius: 50
                        }
    
                        Rectangle {
                            id: rect2
                            height: parent.height
                            width: parent.width - 50
                            color: option1.activeFocus ? "red" : "black"
                            anchors.left: parent.left
                            anchors.leftMargin: 50
                        }
                    }
    
                    Rectangle {
                        id: option2
                        anchors.fill: parent
                        visible: (index === 0 || index === buttonRepeater.model - 1)?false:true
                        color: option2.activeFocus?"red":"black"
                        z: visible?1:0
    
                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                option2.forceActiveFocus()
                            }
                        }
                    }
    
                    Item {
                        id: option3
                        anchors.fill: parent
                        visible: index === buttonRepeater.model - 1
                        z: visible?1:0
    
                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                option3.forceActiveFocus()
                            }
                        }
    
                        Rectangle {
                            id: rect21
                            height: parent.height
                            width: parent.width
                            color: option3.activeFocus ? "red" : "black"
                            radius: 50
                            anchors.right: parent.right
                        }
    
                        Rectangle {
                            id: rect22
                            height: parent.height
                            width: parent.width - 50
                            color: option3.activeFocus ? "red" : "black"
                        }
                    }
    
                    Text {
                        text: index
                        anchors.centerIn: parent
                        color: "yellow"
                        z: 2
                    }
                }
            }
        }

  • Moderators

    @DavidM29
    ok, time to dig in my bag of tricks ;)
    I present RoundedRectangle.qml

    import QtQuick 2.11
    
    Item {
        id:root
    
        property color color: "lightgrey"
        onColorChanged: canVas.requestPaint()
    
        property int radius: root.height/3
        onRadiusChanged: canVas.requestPaint()
    
        property bool topLeftCorner: true
        property bool topRightCorner: false
        property bool bottomRightCorner: true
        property bool bottomLeftCorner: false
    
        onTopLeftCornerChanged: canVas.requestPaint()
        onTopRightCornerChanged: canVas.requestPaint()
        onBottomRightCornerChanged: canVas.requestPaint()
        onBottomLeftCornerChanged: canVas.requestPaint()
    
        property color borderColor: "black"
        onBorderColorChanged: canVas.requestPaint()
    
        property int borderWidth: 1
    
        Canvas {
            id:canVas
            anchors.fill: parent
    
            onPaint: {
                var context = getContext("2d");
                context.reset()
                context.beginPath();
    
                //Start position
                context.moveTo(0,height / 2)
    
                //topLeftCorner
                if(topLeftCorner){
                    context.lineTo(0,radius)
                    context.arcTo(0,0,radius, 0, radius);
                } else {
                    context.lineTo(0,0)
                }
    
                //topRightCorner
                if(topRightCorner){
                    context.lineTo(width - radius, 0)
                    context.arcTo(width, 0, width, radius, radius)
                } else {
                    context.lineTo(width, 0)
                }
    
                //bottomRightCorner
                if(bottomRightCorner) {
                    context.lineTo(width, height-radius)
                    context.arcTo(width, height, width - radius, height, radius)
                } else {
                    context.lineTo(width, height)
                }
    
                //bottomLeftCorner
                if(bottomLeftCorner) {
                    context.lineTo(radius, height)
                    context.arcTo(0, height, 0, height - radius, radius)
                } else {
                    context.lineTo(0, height)
                }
    
                //Close path
                context.lineTo(height / 2)
                context.closePath()
    
                //Draw border
                context.lineWidth = borderWidth
                context.strokeStyle = borderColor
                context.stroke()
    
                //Draw background
                context.fillStyle = color
                context.fill();
            }
        }
    }
    

    to be used as:

    import QtQuick 2.9
    import QtQuick.Window 2.2
    
    Window {
        visible: true
        width: 640
        height: 200
        title: qsTr("Hello World")
    
        Rectangle {
            id: frame
    
            anchors.centerIn: parent
            height: parent.height*2/3
            width:  parent.width *2/3
    
            radius: height/2
    
            color: "lightgrey"
    
    
            ListView{
                id: lView
    
                anchors.fill: parent
                model: [1,2,3]
    
                orientation: ListView.Horizontal
    
                clip:  true
    
                delegate: RoundedRectangle{
                    width: lView.width/lView.count
                    height: lView.height
                    color: lView.currentIndex == index ? "red" : "transparent"
    
                    radius: frame.radius
                    borderColor: "transparent"
                    topLeftCorner: index == 0
                    topRightCorner: index == 2
                    bottomRightCorner: index ==2
                    bottomLeftCorner: index == 0
    
                    Text {
                        anchors.centerIn: parent
                        text: modelData
                    }
    
                    MouseArea{
                        anchors.fill: parent
                        onClicked:{
                            lView.currentIndex = index
                        }
                    }
                }
            }
        }
    }
    

    0_1553681770302_83c19cf6-eec2-4f06-a3ad-6a8b7c217b81-image.png



  • @J.Hilk
    Oh that is perfect !
    I was trying to make this kind of component whit 2 rectangle on top of each others.
    I'm trying to implement that and mak sure it works on my target device.



  • @J.Hilk
    It works like a charm !
    Thank you very much ! I never used any canvas I will have a look into it it seems to be a good tool.


Log in to reply