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