Need help to create a tumbler from scratch



  • Hello,
    I'm coding an app for a device that does not support QuickControls above 1.3 so I don't have the QML tumbler.
    I'm trying to make my own I found some code on the internet that I change a bit but there is something I don't like about it and I don't know how to change it.
    The thing I don't like is that once I have reached the end of my list I do have the begining of my list as the next element like in the QuickControls Tumbler.
    Here is an image of my actual Tumbler :
    0_1529499134220_4bc8d9de-1feb-48f4-88bd-efa42282fbbb-image.png
    As you can see the fisrt tumbler stop at 23 and has no next element.
    The last tumbler start at 0 but has no previous element.
    I would like something like that :
    0_1529499274940_12ffec5b-9c3c-497e-83dd-237f894c3698-image.png

    The tumbler values are based on a ListView.
    Here is the code I have for the moment :

    CustomTumbler.qml :

    import QtQuick 2.3
    
    Rectangle {
        id: root
        height: 200
        width: 200
    
        property var values: [0, 1, 2, 3, 4, 5]
        property var value: 0
        property string suffix: ""
        property int itemHeight: 60
        property int textSize: 30
        property string textColor: "#000000"
        property string indicatorColor: "#grey"
        property int indicatorSize: 2
    
        Component.onCompleted: {
            reloadModel()
        }
    
        onValuesChanged: {
            reloadModel()
        }
    
        onValueChanged: {
            setActive(findItemIndex(root.value), true)
        }
    
        onSuffixChanged: {
            reloadModel()
        }
    
        onTextSizeChanged: {
            reloadModel()
        }
    
        onTextColorChanged: {
            reloadModel()
        }
    
        onIndicatorColorChanged: {
            reloadModel()
        }
    
        onIndicatorSizeChanged: {
            reloadModel()
        }
    
        function findItemIndex(val)
        {
            for(var i = 0; i < tumblerModel.length; i++)
            {
                if(tumblerModel.get(i).val === val && tumblerModel.get(i).hide === false)
                    return i
            }
    
            return -1
        }
    
        function reloadModel()
        {
            var scrollToIndex = 0
    
            tumblerModel.clear()
    
            tumblerModel.append({"val":null,"active":false,"hide":true})
    
            for(var i = 0; i < values.length; i++)
            {
                if(root.value === root.values[i])
                {
                    tumblerModel.append({"val":root.values[i],"active":true,"hide":false})
                    scrollToIndex = i
                }
                else
                {
                    tumblerModel.append({"val":root.values[i],"active":false,"hide":false})
                }
            }
    
            tumblerModel.append({"val":null,"active":false,"hide":true})
    
            tumblerMain.positionViewAtIndex(scrollToIndex, ListView.Center)
        }
    
        function setActive(index, reposition)
        {
            if(index === -1)
                return
            else if(index === 0)
                index = 1
            else if(index === tumblerModel.count - 1)
                index = tumblerModel.count-2
    
            for(var i = 0; i < tumblerModel.count; i++)
            {
                tumblerModel.setProperty(i, "active", false)
            }
    
            tumblerModel.setProperty(index, "active", true)
    
            root.value = root.values[index]
    
            if(reposition)
                tumblerMain.positionViewAtIndex(index, ListView.Center)
        }
    
        ListModel {
            id: tumblerModel
        }
    
        Component {
            id: tumblerDelegate
    
            Rectangle {
                anchors.left: parent.left
                anchors.right: parent.right
                height: root.itemHeight + (hide ? (root.height / 2 - root.itemHeight - root.itemHeight / 2) : 0)
                color: "transparent"
                visible: !hide
    
                Text {
                    text: val + suffix
                    color: root.textColor
                    font.pointSize: root.textSize
                    anchors.centerIn: parent
                    opacity: active ? 1 : 0.3
                }
            }
        }
    
        Rectangle {
            id: indicatorTop
            width: parent.width/1.5
            height: root.indicatorSize
            anchors.centerIn: parent
            anchors.verticalCenterOffset: -(root.itemHeight / 2)
            color: root.indicatorColor
        }
    
        Rectangle {
            id: indicatorBottom
            width: parent.width/1.5
            height: root.indicatorSize
            anchors.centerIn: parent
            anchors.verticalCenterOffset: root.itemHeight / 2
            color: root.indicatorColor
        }
    
        ListView {
            id: tumblerMain
            model: tumblerModel
            delegate: tumblerDelegate
            anchors.fill: parent
            clip: true
            onFlickEnded: {
                console.log("FlickEnd")
                var pos = contentY
                setActive(indexAt(0, contentY + root.height / 2), false)
                positionViewAtIndex(indexAt(0, contentY + root.height / 2), ListView.Center)
                var destPos
                destPos = contentY;
                anim.from = pos;
                anim.to = destPos;
                anim.running = true;
            }
            onDragEnded: {
                console.log("DragEnd")
                var pos = contentY
                console.log(pos)
                console.log(root.height/2)
                console.log(indexAt(0, contentY + root.height / 2))
                setActive(indexAt(0, contentY + root.height / 2), false)
                positionViewAtIndex(indexAt(0, contentY + root.height / 2), ListView.Center)
                var destPos
                destPos = contentY;
                anim.from = pos;
                anim.to = destPos;
                anim.running = true;
            }
        }
    
        NumberAnimation { id: anim; target: tumblerMain; property: "contentY"; duration: 100}
    }
    
    

    Main.qml :

    import QtQuick 2.9
    import QtQuick.Window 2.2
    import QtQuick.Controls 2.2
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
    
        CustomTumbler {
                id: t1
                radius: 0
                width: 120
                suffix: ""
                value: 0
    
                Component.onCompleted: {
                    var vals = []
                    for(var i = 0; i < 24; i++)
                        vals[i] = i
    
                    values = vals
                }
            }
    
            CustomTumbler {
                id: t2
                anchors.left: t1.right
                radius: 0
                width: 120
                suffix: ""
                value: 0
    
                Component.onCompleted: {
                    var vals = []
                    for(var i = 0; i < 60; i++)
                        vals[i] = i
    
                    values = vals
                }
            }
    
            CustomTumbler {
                id: t3
                anchors.left: t2.right
                radius: 0
                width: 120
                suffix: ""
                value: 0
    
                Component.onCompleted: {
                    var vals = []
                    for(var i = 0; i < 60; i++)
                        vals[i] = i
    
                    values = vals
                }
            }
    }
    

    Does anybody know how could I make a loop on my values ?


  • Moderators

    @DavidM29
    The "official" QML Tumbler has a wrap property which does what you want.



  • @raven-worx
    I can not use the "official" QML Tumbler because the device I programming for does not support Qt QuickControls 2 only Qt QuickControls 1.3. That is the reasons why I'm trying to make my own Tumbler


  • Moderators

    @DavidM29 said in Need help to create a tumbler from scratch:

    does not support Qt QuickControls 2

    why actually?
    Are you limited by the Qt version available?



  • It is an embedded device which work only with Qt 5.4 for now. And according to the company providing this device it will not be change soon. So I'm making a few controls of my own.



  • I'm actually trying to change my ListView to a Pathview which seems to have the feature I want but I'm not able to make it work for now. So if anybody have a clue please share it with me.



  • From now I have made this which is close to a tumbler there a fiew things to change but it works quite well :

    Rectangle {
    
        id: container
        width: 100
        height: 200
    
        Rectangle{  //This rectangle is optional it is the lower bar on the middle of the View
            anchors.centerIn: parent
            anchors.verticalCenterOffset: (container.height / 10)
            width: parent.width/2
            height: 2
            color: "black"
        }
    
        Rectangle{ //This rectangle is optional it is the upper bar on the middle of the View
            anchors.centerIn: parent
            anchors.verticalCenterOffset: -(container.height / 10)
            width: parent.width/2
            height: 2
            color: "black"
        }
    
        ListModel {
            id: tumblerModel
            ListElement{ num:"01" }
            ListElement{ num:"02" }
            ListElement{ num:"03" }
            ListElement{ num:"04" }
            ListElement{ num:"05" }
            ListElement{ num:"06" }
            ListElement{ num:"07" }
            ListElement{ num:"08" }
            ListElement{ num:"09" }
            ListElement{ num:"10" }
        }
    
        Component {
            id: tumblerDelegate
    
            Item {
                id: itm
                anchors.left: parent.left
                anchors.right: parent.right
                height: parent.height/5
                scale: PathView.iconScale
                opacity: PathView.iconOpacity
    
                Text {
                    text: num;
                    color: itm.PathView.isCurrentItem ? "red" : "black"
                    font.pointSize: 12
                    anchors.centerIn: parent
                    //opacity: active ? 1 : 0.3
                }
                MouseArea{
                    id: itemMouseArea
                    anchors.fill: parent
                    onClicked: {
                        pathView.currentIndex = index
    
                    }
                }
            }
        }
    
        PathView {
            id: pathView
            anchors.fill: parent
            model: tumblerModel
            pathItemCount: 5  //Show only five item from the Model
            preferredHighlightBegin: 0.5  //It is used to center the selected item 
            preferredHighlightEnd: 0.5  //It is used to center the selected item 
            highlightRangeMode: PathView.StrictlyEnforceRange
            delegate: tumblerDelegate
            path: Path {   //This part could probably be optimized I do not really understand everything of what I'm doing here but it works 
                startX: parent.width/2; startY: 0
                PathAttribute { name: "iconScale"; value: 0.7 }
                PathAttribute { name: "iconOpacity"; value: 0.2 }
                PathLine {x: parent.width/2; y: container.height/2 }
                PathAttribute { name: "iconScale"; value: 2 }
                PathAttribute { name: "iconOpacity"; value: 1 }
                PathLine {x: parent.width/2; y: container.height }
            }
        }
    }
    

    You will need to implement the selected value retrieving and the variable model and then it should be working.
    Here is the actual result :
    0_1529572117470_a548e4ae-c59e-4907-8884-50bb39f70cb8-image.png


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.