Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Need help to create a tumbler from scratch

Need help to create a tumbler from scratch

Scheduled Pinned Locked Moved Solved QML and Qt Quick
7 Posts 2 Posters 1.8k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    DavidM29
    wrote on last edited by
    #1

    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 ?

    raven-worxR 1 Reply Last reply
    0
    • D DavidM29

      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 ?

      raven-worxR Offline
      raven-worxR Offline
      raven-worx
      Moderators
      wrote on last edited by
      #2

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

      --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
      If you have a question please use the forum so others can benefit from the solution in the future

      1 Reply Last reply
      0
      • D Offline
        D Offline
        DavidM29
        wrote on last edited by
        #3

        @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

        raven-worxR 1 Reply Last reply
        0
        • D DavidM29

          @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

          raven-worxR Offline
          raven-worxR Offline
          raven-worx
          Moderators
          wrote on last edited by
          #4

          @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?

          --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
          If you have a question please use the forum so others can benefit from the solution in the future

          1 Reply Last reply
          0
          • D Offline
            D Offline
            DavidM29
            wrote on last edited by
            #5

            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.

            1 Reply Last reply
            0
            • D Offline
              D Offline
              DavidM29
              wrote on last edited by
              #6

              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.

              1 Reply Last reply
              0
              • D Offline
                D Offline
                DavidM29
                wrote on last edited by DavidM29
                #7

                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

                1 Reply Last reply
                1

                • Login

                • Login or register to search.
                • First post
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • Users
                • Groups
                • Search
                • Get Qt Extensions
                • Unsolved