Hide first or last item in PathView - qt

I wrote a simple carousel in QML. I run Qt 6.2.0 under Ubuntu 20.04.
import QtQuick
PathView {
id: view
property int item_width: 864
property int item_gap: 250
anchors.fill: parent
anchors.bottomMargin: 150
anchors.topMargin: 50
pathItemCount: 3
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
highlightRangeMode: PathView.StrictlyEnforceRange
highlightMoveDuration: 1000
snapMode: PathView.SnapToItem
rotation: -90
model: modelContent
delegate: DelegateContent { }
path: Path {
startX: -item_gap; startY: view.height / 2
PathAttribute { name: "iconScale"; value: 0.7 }
PathAttribute { name: "iconOpacity"; value: 0.1 }
PathAttribute { name: "iconOrder"; value: 0 }
PathLine {x: view.width / 2; y: view.height / 2; }
PathAttribute { name: "iconScale"; value: 1 }
PathAttribute { name: "iconOpacity"; value: 1 }
PathAttribute { name: "iconOrder"; value: 9 }
PathLine {x: view.width + item_gap; y: view.height / 2; }
}
Component.onCompleted: {
gesture.gestureFired.connect(gestureFired)
}
onCurrentIndexChanged: {
itemAtIndex(0).visible = currentIndex < (count - 1)
}
function gestureFired(type) {
switch (type) {
case 1:
if (view.currentIndex > 0) view.decrementCurrentIndex();
break;
case 2:
if (view.currentIndex < view.count - 1) view.incrementCurrentIndex();
break;
}
}
}
It works very well. The change of the current index is done programmatically only (gestureFired signal from C++). As you can see there is no circular behavior.
Hence, I want to hide the first item (when the last one is selected) or the last item (when the first is selected).
I tried with the code above:
onCurrentIndexChanged: {
itemAtIndex(0).visible = currentIndex < (count - 1)
}
The idea is: if the currentIndex is the last element ( = count - 1) the visible property of the first item should be false.
But it does not work:
TypeError: Value is null and could not be converted to an object
Which "Value" is it talking about?
I debugged the currentIndex and count and they are both valid (4 and 5).
Which is the correct way to achieve such a behavior?

A simple solution is to do the binding in the delegate:
import QtQuick
Column {
id: root
required property int index
// mapping model roles
required property string name
visible: _internals.calculateVisibility()
QtObject {
id: _internals
function calculateVisibility() {
console.log(index, root.PathView.isCurrentItem, root.PathView.view.currentIndex)
if ((root.index === (root.PathView.view.count - 1)) && (root.PathView.view.currentIndex === 0))
return false;
else if ((root.index === 0) && (root.PathView.view.currentIndex === (root.PathView.view.count - 1)))
return false;
return true;
}
}
Text {
id: nameText
text: root.name
font.pointSize: 16
color: root.PathView.isCurrentItem ? "red" : "blue"
}
}

Related

QML need to create component with bool property all list elements were within limits

I was thinking I need a component similar to ListModel, but I need to extend it to expose a readonly bool property such as "all list elements were within minimum and maximum limit" so I can do logic outside the component the determine certain things. How should I go about doing this extending a boolean property based on model's contents?
I guess naive way is to just add the qml property and do javascript loop on QML side to check all model contents but that might not be so good performance
Have you considered DelegateModel? It allows you to create "views" on your ListModel so you can control what you want to be displayed via the filterOnGroup property.
It is rather difficult to comprehend, but, in the following example, I have a ListModel containing 5 cities. When you start changing the RangeSlider the 5 cities will be filtered based on the minimum/maximum population selected. This works by updating the boolean function filter on the DelegateModel to reflect the cities that are now visible.
property var filter: model => model.pop >= rangeSlider.first.value
&& model.pop <= rangeSlider.second.value
Here's the full code snippet:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
Page {
anchors.fill: parent
ColumnLayout {
anchors.fill: parent
Label { text: qsTr("States") }
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: DelegateModel {
id: filterDelegateModel
property int updateIndex: 0
property var filter: model => model.pop >= rangeSlider.first.value
&& model.pop <= rangeSlider.second.value
onFilterChanged: Qt.callLater(update)
model: us_states
groups: [
DelegateModelGroup {
id: allItems
name: "all"
includeByDefault: true
onCountChanged: {
if (filterDelegateModel.updateIndex > allItems.count) filterDelegateModel.updateIndex = allItems.count;
if (filterDelegateModel.updateIndex < allItems.count) Qt.callLater(update, filterDelegateModel.updateIndex);
}
},
DelegateModelGroup {
id: visibleItems
name: "visible"
}]
filterOnGroup: "visible"
delegate: Frame {
id: frame
width: ListView.view.width - 20
background: Rectangle {
color: (frame.DelegateModel.visibleIndex & 1) ? "#f0f0f0" : "#e0e0e0"
border.color: "#c0c0c0"
}
RowLayout {
width: parent.width
Text {
text: (frame.DelegateModel.visibleIndex + 1)
color: "#808080"
}
Text {
Layout.fillWidth: true
text: model.state
}
Text {
text: qsTr("pop: %1 M").arg((pop / 1000000).toFixed(2))
}
}
}
function update(startIndex) {
startIndex = startIndex ?? 0;
if (startIndex < 0) startIndex = 0;
if (startIndex >= allItems.count) {
updateIndex = allItems.count;
return;
}
updateIndex = startIndex;
if (updateIndex === 0) {
allItems.setGroups(0, allItems.count, ["all"]);
}
for (let ts = Date.now(); updateIndex < allItems.count && Date.now() < ts + 50; updateIndex++) {
let visible = !filter || filter(allItems.get(filterDelegateModel.updateIndex).model);
if (!visible) continue;
allItems.setGroups(updateIndex, 1, ["all", "visible"]);
}
if (updateIndex < allItems.count) Qt.callLater(update, updateIndex);
}
Component.onCompleted: Qt.callLater(update)
}
}
Label { text: "Population Range" }
RangeSlider {
id: rangeSlider
Layout.fillWidth: true
from: 0
to: 100000000
first.value: 1
first.onMoved: Qt.callLater(filterDelegateModel.update)
second.value: 100000000
second.onMoved: Qt.callLater(filterDelegateModel.update)
stepSize: 1000000
}
Label { text: qsTr("Minimum %1 M").arg((rangeSlider.first.value / 1000000).toFixed(2)) }
Label { text: qsTr("Maximum %1 M").arg((rangeSlider.second.value / 1000000).toFixed(2)) }
}
ListModel {
id: us_states
ListElement { state:"California"; pop: 39350000 }
ListElement { state:"Texas"; pop: 28640000 }
ListElement { state:"New York"; pop: 8380000 }
ListElement { state:"Nevada"; pop: 3030000 }
ListElement { state:"Las Vegas"; pop: 644000 }
}
}
You can Try it Online!
I have refactored the above into a FilterDelegateModel reusable component. Feel free to check it out:
https://github.com/stephenquan/qt5-qml-toolkit
https://github.com/stephenquan/qt5-qml-toolkit/wiki/FilterDelegateModel

How to split a rectangle into 2 equal parts using QML animation?

Scenario: A rectangle moves horizontally with some speed and at certain position it should be split into 2 parts and then both should move separately with same speed.
I have tried adding new rectangle at that "certain" position and used opacity property and animated movement of 2 separate rectangles but not able to achieve what i wished for.
Is there any other way like when first rectangle reaches that certain position
while moving in x direction can that send a signal and 2nd rectangle start moving which should be invisible till that time.
Rectangle id: material (1st rectangle),
Rectangle id: material1(2nd rectangle)
code which i have written:
Rectangle {
id: material1
x:291
y:187
width: 71
height: 10
color: "#ff5930"
states: [
State{
name: "Visible"
PropertyChanges{target: material1; opacity: 1.0}
PropertyChanges{target: material1; visible: true}
},
State{
name:"Invisible"
PropertyChanges{target: material1; opacity: 0.0}
PropertyChanges{target: material1; visible: false}
}
]
transitions: [
Transition {
from: "Invisible"
to: "Visible"
PropertyAnimation {
target: material1
property: opacity
duration: 11000
}
PropertyAnimation {
target: material1
property: visible
duration: 0
}
}
]
SequentialAnimation on x {
loops: Animation.Infinite
PropertyAnimation{ from: 291 ; to: 1008
duration: 11000
}
}
}
Rectangle {
id: material
x: 159
y: 187
width: 71
height: 10
color: "#ff5930"
SequentialAnimation on x {
loops: Animation.Infinite
PropertyAnimation{ from: 159 ; to: 1008
duration: 11000
}
}
}
I see no code where you try to create 2 items from a given one. You have to copy an item before deletion keeping direction on whatever you want. I would do that in the following way:
MyItem.qml
import QtQuick 2.11
Rectangle {
signal itemBeforeDelete(Item item)
signal itemDestroyed(Item item)
id: item
width: 50
height: 50
radius: 25
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
x: 50 + Math.round(Math.random() * 700)
y: 50 + Math.round(Math.random() * 500)
property int dx: Math.round(Math.random() * 10) - 5
property int dy: Math.round(Math.random() * 10) - 5
property int maxTicks: 100 + Math.round(Math.random() * 500)
function tick() {
item.maxTicks --;
x += item.dx;
y += item.dy;
if(x <= 0|| ((x + width)>= parent.width - dx))
item.dx *= (-1);
if(y <= 0|| ((y + height)>= parent.height - dy))
item.dy *= (-1);
if(item.maxTicks == 0) {
item.state = "target"
}
}
state: "initial"
states: [
State {
name: "initial"
PropertyChanges { target: item; opacity: 1 }
},
State {
name: "target"
PropertyChanges { target: item; opacity: 0 }
}
]
transitions: Transition {
from: "initial"
to: "target"
SequentialAnimation {
ScriptAction { script: item.itemBeforeDelete(item); }
PropertyAnimation { target: item; property:"opacity"; duration: 1000 }
ScriptAction { script: item.itemDestroyed(item); }
}
}
}
main.qml
import QtQuick 2.11
import QtQuick.Controls 2.4
ApplicationWindow {
id: mainWindow
width: 800
height: 600
visible: true
Timer {
id: timer
interval: 20
repeat: true
running: true
onTriggered: {
for(var i = 0;i < mainWindow.contentItem.children.length;i++)
{
mainWindow.contentItem.children[i].tick();
}
}
}
function createChildrenFromItem(item) {
var component = Qt.createComponent("MyItem.qml");
if (component.status === Component.Ready)
{
var offsetx = Math.round(Math.random() * 4) - 2;
var offsety = Math.round(Math.random() * 4) - 2;
var params = item ? {
"x": item.x,
"y": item.y,
"dx": item.dx + offsetx,
"dy": item.dy + offsety
} :{}
var obj = component.createObject(mainWindow.contentItem, params);
obj.itemBeforeDelete.connect(onBeforeDelete);
obj.itemDestroyed.connect(onDestroyed);
}
}
function onBeforeDelete(item)
{
createChildrenFromItem(item);
createChildrenFromItem(item);
}
function onDestroyed(item)
{
item.destroy();
}
Component.onCompleted: {
createChildrenFromItem();
}
}

QML: in loading images dynamically

I am trying to load different images dynamically based on a button press.
Rectangle {
id: rect
width: 800
height: 600
color: "black"
x: 300
y: 10
MediaPlayer {
id: mediaplayer
source: "images/video10.avi"
autoPlay: true
onError: videoPage1.sendToPost(mediaplayer.errorString)
}
VideoOutput {
id: videoOutput
anchors.fill: parent
source: mediaplayer
fillMode: VideoOutput.Stretch
}
MouseArea {
id: playArea
anchors.fill: parent
onPressed: {
if(!mediaplayer.hasVideo){
console.log("No media");
}
pressX = mouse.x
pressY = mouse.y
}
onReleased: {
releaseX = mouse.x
releaseY = mouse.y
doSomething();
roi_flag=0;
}
}
}
Component {
id: highlightComponent;
Rectangle {
color: "transparent";
border.color: "black"
border.width: 2
property alias text:t1.text;
property alias ss: image.source;
Text {
id: t1
text: "click"
anchors.centerIn: parent
width: 5
}
Image {
id:image
// width: 130; height: 100
// source: (direction_flag ? "images/top.png" : "images/right.png")
source : ss
anchors.centerIn: parent
}
}
}
function doSomething(){
console.log(rOI);
count++;
var obj = 'highlightItem' + count;
var dir;
if(direction_flag == 0){
if((pressX - releaseX) > 0){
dir= "images/left.png";
}else if((pressX - releaseX) < 0){
dir="images/right.png";
}
}else{
if((pressY - releaseY) > 0){
dir="images/top.png";
}else if((pressY - releaseY) < 0){
dir="images/down.png";
}
}
highlightComponent.incubateObject (rect, {
"x" : pressX,"y":pressY,width:releaseX - pressX,height: releaseY - pressY,text:count,ss:dir});
}
The method works when changing the text but its not working when changing the image, I am getting the error "QML Image: Protocol "" is unknown".
Any suggestions will be helpful, thanks.

QML-Move item with arrow keys

I'm using Qt version 5.6.0 from windows 8.1. I draw a shape that has 4 sides and the ability to drag its apexes.I want to move apexes of shape with arrow keys.I use this code but it does not work.
Point.qml:
Item {
id: root
signal dragged()
property alias color :point.color
Rectangle {
id:point
anchors.centerIn: parent
width: 20
height: 20
opacity: 0.2
MouseArea {
anchors.fill: parent
drag.target: root
onPositionChanged: {
if(drag.active) {
dragged()
}
}
onClicked: point.focus=true;
}
// Keys.onPressed: {
// console.log("move");
// if (event.key === Qt.Key_Left) {
// console.log("move left");
// event.accepted = true;
// point.x-=1;
// }
// }
// Keys.onRightPressed: {
// console.log("move");
// point.x+=1;
// }
// Keys.onLeftPressed: {
// console.log("move");
// point.x-=1;
// }
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Left: point.x-=1;
break;
case Qt.Key_Right: point.x+=1;
break;
case Qt.Key_Up: point.y-=1;
break;
case Qt.Key_Down: point.y+=1;
break;
}
}
focus: true;
}}
main.qml:
Point {
id: pointA
x: 50
y: 50
}
Point {
id: pointB
x: 250
y: 50
}
Point {
id: pointC
x: 250
y: 250
}
Point {
id: pointD
x: 50
y: 250
}
Item {
anchors.fill: parent
Canvas {
id: canvas
anchors.fill: parent
onPaint: {
var ctx = canvas.getContext('2d');
ctx.moveTo(pointA.x, pointA.y);
ctx.lineTo(pointB.x, pointB.y);
ctx.lineTo(pointC.x, pointC.y);
ctx.lineTo(pointD.x, pointD.y);
ctx.lineTo(pointA.x, pointA.y);
ctx.stroke();
}
Component.onCompleted: {
pointA.dragged.connect(repaint)
pointB.dragged.connect(repaint)
pointC.dragged.connect(repaint)
pointD.dragged.connect(repaint)
}
function repaint() {
var ctx = getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
requestPaint()
}
}
}
Update:
main.qml:
PhotoPreview {
id : photoPreview
anchors.fill : parent
//focus:true //visible
visible: capture
}
photopreview.qml:
Item {
id:mywin
property real defaultSize:mywin.width
property var currentFrame: undefined
property real surfaceViewportRatio: 1.5
focus: true
ScrollView {
anchors.fill: parent
flickableItem.interactive: true
frameVisible: true
highlightOnFocus: true
Flickable {
id: flick
anchors.fill: parent
contentWidth: parent.width
contentHeight: parent.height
property alias source :image.source
signal closed
Rectangle {
id: photoFrame
width: parent.width
height: parent.height
color:"transparent"
scale:defaultSize / parent.width
Behavior on scale { NumberAnimation { duration: 200 } }
Behavior on x { NumberAnimation { duration: 200 } }
Behavior on y { NumberAnimation { duration: 200 } }
smooth: true
antialiasing: true
Image {
id:image
anchors.fill: parent
fillMode: Image.PreserveAspectFit
smooth: true
}
PinchArea {
anchors.fill: parent
pinch.target: photoFrame
pinch.minimumRotation: -360
pinch.maximumRotation: 360
pinch.minimumScale: 0.1
pinch.maximumScale: 10
pinch.dragAxis: Pinch.XAndYAxis
property real zRestore: 0
onSmartZoom: {
if (pinch.scale > 0) {
photoFrame.rotation = 0;
photoFrame.scale = Math.min(mywin.width, mywin.height) / Math.max(image.sourceSize.width, image.sourceSize.height) * 0.85
photoFrame.x = flick.contentX + (flick.width - photoFrame.width) / 2
photoFrame.y = flick.contentY + (flick.height - photoFrame.height) / 2
zRestore = photoFrame.z
photoFrame.z = ++mywin.highestZ;
} else {
photoFrame.rotation = pinch.previousAngle
photoFrame.scale = pinch.previousScale
photoFrame.x = pinch.previousCenter.x - photoFrame.width / 2
photoFrame.y = pinch.previousCenter.y - photoFrame.height / 2
photoFrame.z = zRestore
--mywin.highestZ
}
}
MouseArea {
id: dragArea
hoverEnabled: true
anchors.fill: parent
drag.target: photoFrame
scrollGestureEnabled: false // 2-finger-flick gesture should pass through to the Flickable
onPressed: {
photoFrame.z = ++mywin.highestZ;
}
onWheel: {
if (wheel.modifiers & Qt.ControlModifier) {
photoFrame.rotation += wheel.angleDelta.y / 120 * 5;
if (Math.abs(photoFrame.rotation) < 4)
photoFrame.rotation = 0;
} else {
photoFrame.rotation += wheel.angleDelta.x / 120;
if (Math.abs(photoFrame.rotation) < 0.6)
photoFrame.rotation = 0;
var scaleBefore = photoFrame.scale;
photoFrame.scale += photoFrame.scale * wheel.angleDelta.y / 120 / 10;
}
}
}
}
Point {
id: pointA
x: image.width/4
y: image.height/4
color: "blue"
}
Point {
id: pointB
x: image.width/2
y: image.height/2
color: "blue"
}
Point {
id: pointD
x: image.width/4
y: image.height/2
color: "red"
}
Point {
id: pointC
x: image.width/2
y: image.height/4
color: "red"
}
Item {
anchors.fill: parent
Canvas {
id: canvas
anchors.fill: parent
onPaint: {
//...
}
Component.onCompleted: {
//...
}
function repaint() {
//..
}
}
}
}
}
}}
You are using ScrollView that inherit FocusScope, which needs to have focus:true to forward it.
You instead are setting focus to PhotoPreview, which is plain Item that are not supposed to be focused.
So you need to simple remove focus:visible and set it to ScrollView.
The better fix is to remove that redundant Item, and leave ScrollView as PhotoPreview root item, with all its properties and signals.
In Point.qml the key event handler tries to change the x, y of point, however point is a rectangle that anchors.centerIn: parent. The key event handler should change root x, y instead:
//in Point.qml
case Qt.Key_Left:
root.x-=1;
break;
Point now changes it's position when keyboard event is triggered. Next, Point needs to notify the Canvas in main.qml to repaint. Point can emit dragged signal after changing it's x, y:
//in Point.qml
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Left:
root.x-=1;
dragged();
break;
case Qt.Key_Right:
root.x+=1;
dragged();
break;
case Qt.Key_Up:
root.y-=1;
dragged();
break;
case Qt.Key_Down:
root.y+=1;
dragged();
break;
}
}

QML: setProperty has no effect

When I try to change a property value of an item contained into a ListModel the following code has no effect:
Main.qml
import QtQuick 2.0
Item {
anchors.fill: parent
ListModel { id: modelCrayon }
Component.onCompleted: {
for (var i = 0; i < 10; i++)
modelCrayon.append( { _tag: i, _source: "resources/crayon-green.png", _selected: false } )
}
Column {
x: -170
spacing: 0
Repeater {
model: modelCrayon
delegate: Crayon {
tag: _tag
source: _source
selected: _selected
onCrayonSelected: {
for (var i = 0; i < modelCrayon.count; i++) {
if (i == tag) continue;
modelCrayon.setProperty(i, "_selected", false);
}
}
}
}
}
}
Crayon.qml
import QtQuick 2.0
Image {
property bool selected
property int tag
signal crayonSelected()
id: crayon
smooth: true
fillMode: Image.PreserveAspectFit
onSelectedChanged: console.debug(tag, selected)
MouseArea {
anchors.fill: parent
onClicked: {
selected = !selected
if (selected) crayonSelected()
}
}
states: State {
name: "selected"; when: selected == true
PropertyChanges { target: crayon; x: 30 }
}
transitions: Transition {
from: ""; to: "selected"
PropertyAnimation { property: "x"; duration: 500; easing.type: Easing.InOutQuad }
}
}
Nothing is shown on console, so the "selected" var is never changed.
I'm sure there's something obvious I'm missing.
By the way, is there a smarter way to use a ListModel as a OptionBox? I mean I want only ONE item at time must have the selected property == true. Or, in other words, keep tracks of the selected index.
This is a working code to achieve what I asked. But it doens't answer why the property was not set.
ListView {
id: list
anchors.verticalCenter: parent.verticalCenter
height: parent.height
x: -150
spacing: 0
orientation: ListView.Vertical
focus: true
model: modelCrayon
delegate: Crayon {
id: delegate
source: _source
selected: ListView.isCurrentItem
MouseArea {
anchors.fill: parent
onClicked: list.currentIndex = index
}
}
}
I have tested your sample code (the Column version), and it works well with Qt 5.4 / Windows 7 64bit.
What is your running environment?

Resources