I would like to create a slideshow showing 3 items with a picture and a label for each, the item in the middle being highlighted (picture is bigger and a description text appears below the label).
When a corresponding arrow is clicked, I would like the items to "slide" instead of just appearing where they should. Unfortunately, the Behavior on x {
NumberAnimation{...}} code in the delegate does not do this.
Here is my code:
import QtQuick 2.7
import QtQuick.Window 2.0
Window {
id: display
width: 500
height: 300
visible: true
Item {
id: conteneur
anchors.leftMargin: 50
height: display.height / 1.2
width: display.width / 1.2
anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
id: boutonAvant
height: conteneur.height
anchors.verticalCenter: parent.verticalCenter
width: 68
x: -50
color: "transparent"
z: 1
Text {
id: pictureAv
anchors.centerIn: parent
text: "<"
font.pixelSize: 90
}
MouseArea {
id: buttonAvMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: listview.decrementCurrentIndex()
}
}
ListView {
id: listview
clip: true
orientation: ListView.Horizontal
width: conteneur.width
height: conteneur.height / 1.2
anchors.centerIn: conteneur
model: myListModel
delegate: myDelegate
maximumFlickVelocity: 700
snapMode: ListView.SnapToItem
highlightFollowsCurrentItem: true
highlightRangeMode: ListView.StrictlyEnforceRange
preferredHighlightBegin: conteneur.width * 0.3
preferredHighlightEnd: conteneur.width * 0.3 + conteneur.width * 0.4
onCurrentIndexChanged: {
positionViewAtIndex(currentIndex, ListView.SnapPosition)
}
Component.onCompleted: {
currentIndex = 1
}
}
Rectangle {
id: boutonApres
height: conteneur.height
anchors.verticalCenter: parent.verticalCenter
x: conteneur.width - 10
width: 68
color: "transparent"
Text {
id: pictureAp
anchors.centerIn: parent
text: ">"
font.pixelSize: 90
}
MouseArea {
id: buttonApMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: listview.incrementCurrentIndex()
}
}
}
ListModel {
id: myListModel
ListElement {
name: "rectangle 0"
desc: "blabla"
mycolor: "green"
}
ListElement {
name: "rectangle 1"
desc: "blabla"
mycolor: "blue"
}
ListElement {
name: "rectangle 2"
desc: "blabla"
mycolor: "lightblue"
}
ListElement {
name: "rectangle 3"
desc: "blabla, \n with several lines for test \n and more lines \n and more lines"
mycolor: "gold"
}
}
Component {
id: myDelegate
Rectangle {
id: cadre
opacity: listview.currentIndex === index ? 1 : 0.5
anchors.top: parent.top
anchors.topMargin: listview.currentIndex === index ? 0 : 35
width: listview.currentIndex === index ? listview.width * 0.4 : listview.width * 0.3
height: conteneur.height
border.color: mycolor
color: "transparent"
Behavior on x {
NumberAnimation {
duration: 800
}
}
}
}
}
ListView inherits Flickable which uses contentX and contentY to govern what's visible. The model Rectangles don't actually move.
I would try a Behavior on ListView's contentX. Note that the documentation for positionViewAtIndex says not manipulate those directly because the math on them is not predictable – but a behavior on them may work.
I finally had some result using this :
//In bouton Avant :
MouseArea{
id: boutonAvant
anchors.fill: parent
hoverEnabled: true
onClicked: {
pictureAp.visible = true;
var oldPos = listview.contentX;
listview.decrementCurrentIndex();
var newPos = oldPos - listview.width*0.3; // listview.width*0.3 is the width of one item that is not the current one
if(listview.currentIndex == 0){
pictureAv.visible = false;
}
anim.running = false
anim.from = oldPos;
anim.to = newPos;
anim.running = true;
}
}
}
The ListView becomes :
ListView{
id: listview
clip: true
orientation: ListView.Horizontal
width: conteneur.width
height: conteneur.height/1.2
anchors.centerIn: conteneur
model: myListModel
delegate: myDelegate
Component.onCompleted: {
currentIndex = 1;
}
}
NumberAnimation { id: anim; target: listview; property: "contentX"; duration: 800 }
And boutonApres is similar to boutonAvant with :
MouseArea{
id: buttonApMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
pictureAv.visible = true;
var oldPos = listview.contentX;
listview.incrementCurrentIndex();
var newPos = oldPos + listview.width*0.3;
if(listview.currentIndex == listview.count-1){
pictureAp.visible = false;
}
anim.running = false
anim.from = oldPos;
anim.to = newPos;
anim.running = true;
}
}
It works fines when items being 'slided' are in the middle of the listview but when I get to the first item (on the last click on the left arrow), or to the last item (on the last click on the right arrow), I get a disturbing 'flick' as if the listview was trying to move at two places at the same time, following 2 different orders. But I can't see where this could come from...
Related
I'm trying to create accordion qml control like this.
First I thought that I can use combobox and customize it but now I think it is impossible.
Is there any standerd control that I can use? If not can you help me with controls structure?
Just playing with QML
PanelItem.qml
import QtQuick 2.7
import QtQuick.Layouts 1.2
Item {
default property var contentItem: null
property string title: "panel"
id: root
Layout.fillWidth: true
height: 30
Layout.fillHeight: current
property bool current: false
ColumnLayout {
anchors.fill: parent
spacing: 0
Rectangle {
id: bar
Layout.fillWidth: true
height: 30
color: root.current ? "#81BEF7" : "#CEECF5"
Text {
anchors.fill: parent
anchors.margins: 10
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: root.title
}
Text {
anchors{
right: parent.right
top: parent.top
bottom: parent.bottom
margins: 10
}
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
text: "^"
rotation: root.current ? "180" : 0
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
root.current = !root.current;
if(root.parent.currentItem !== null)
root.parent.currentItem.current = false;
root.parent.currentItem = root;
}
}
}
Rectangle {
id: container
Layout.fillWidth: true
anchors.top: bar.bottom
implicitHeight: root.height - bar.height
clip: true
Behavior on implicitHeight {
PropertyAnimation { duration: 100 }
}
}
Component.onCompleted: {
if(root.contentItem !== null)
root.contentItem.parent = container;
}
}
}
usage:
import QtQuick 2.7
import QtQuick.Layouts 1.2
import QtQuick.Window 2.0
Window {
visible: true
width: 400
height: 400
ColumnLayout {
anchors.fill: parent
spacing: 1
property var currentItem: null
PanelItem {
title: "Panel 1"
Rectangle {
color: "orange"
anchors.fill: parent
}
}
PanelItem {
title: "Panel 2"
Rectangle {
color: "lightgreen"
anchors.fill: parent
}
}
PanelItem {
title: "Panel 3"
Rectangle {
color: "lightblue"
anchors.fill: parent
}
}
PanelItem {
title: "Panel 4"
Rectangle {
color: "yellow"
anchors.fill: parent
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
What about using this open source component which I did here
Accordion component and used here Accordion component example.
You only need to initialize:
Components.Accordion {
id: acordion
anchors.fill: parent
anchors.margins: 10
}
And create the data dynamically like this:
propertyAcordion.model = [
{
'menuTitle': value,
'children': [
{
'menuTitle': value,
'children': [
...
Updating the MouseArea click part as below with some added condition. Thanks to folibis for this qml accordian menu.
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
root.current = !root.current;
if(root.parent.currentItem !== null) {
if(root.parent.currentItem !== root)
root.parent.currentItem.current = false;
}
root.parent.currentItem = root;
}
}
I have this simple qml code
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtQml.Models 2.2
ApplicationWindow {
visible: true
width: 300
height: 120
title: qsTr("Hello World")
Rectangle {
anchors.fill: parent;
ListView{
id: timeline
anchors.fill: parent
orientation: ListView.Horizontal
model: visualModel
delegate: timelineDelegate
moveDisplaced: Transition {
NumberAnimation{
properties: "x,y"
duration: 200
}
}
DelegateModel {
id: visualModel
model: timelineModel
delegate: timelineDelegate
}
Component {
id: timelineDelegate
MouseArea {
id: dragArea
width: 100; height: 100
property bool held: false
drag.target: held ? content : undefined
drag.axis: Drag.XAxis
onPressAndHold: held = true
onReleased: {
held = false
var listOnModel = "{";
for(var i = 0; i < timelineModel.count; i++){
listOnModel += timelineModel.get(i).colore + ", "
}
console.log("List: " + listOnModel + "}");
}
Rectangle {
id: content
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
width: 100
height: 100
color: colore
opacity: dragArea.held ? 0.8 : 1.0
Text{
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
text: index
font.pixelSize: 20
}
Drag.active: dragArea.held
Drag.source: dragArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
states: State{
when: dragArea.held
ParentChange { target: content; parent: timeline }
AnchorChanges {
target: content
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
}
DropArea {
anchors.fill: parent
onEntered: {
visualModel.items.move( drag.source.DelegateModel.itemsIndex, dragArea.DelegateModel.itemsIndex);
}
}
}
}
ListModel {
id: timelineModel
// #disable-check M16
ListElement { colore: "blue" }
// #disable-check M16
ListElement { colore: "orange" }
// #disable-check M16
ListElement { colore: "green" }
}
}
}
}
Here we have a simple list of colored draggable rectangles. In the center of every rectangle is shown the actual index, that this component has inside the model.
As you can see, after the drop event, the index for every item doesn't change, and the order of the items inside the model is still the same. Is there a way to retrieve the new order of the list after a drag and drop event occurred?
You don't reorder the ListModel, but the items of your DelegateModel.
So you need to use this code instead:
onReleased: {
held = false
var listOnModel = "{";
for(var i = 0; i < visualModel.items.count; i++){
listOnModel += visualModel.items.get(i).model.colore + ", "
}
console.log("List: " + listOnModel + "}");
}
I have a problem, which can be seen on the attached screenshot
There is ApplicationWindow, which has header and ListView which is used in horizontal layout. Each item of list should be one page of application. Unfortunatelly, the width of base page is not set correctly to fill width of its parent (white background, not the grey one).
Here is the code of main page, it should load Login page - it is shown on the image.
ApplicationWindow {
id: root_window
title: Style.applicationName
visible: true
color: "white"
width: Style.windowWidth
height: Style.windowHeight
ColumnLayout {
id: root_layout
spacing: 0
width: root_window.width
height: root_window.height
SmonHeader {
id: smon_user_app_header
height: Style.headerHeight
anchors.top: parent.top
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
}
Component.onCompleted: {
console.log("Main width: " + width);
}
ListView {
id: navigation
width: parent.width
height: parent.height
orientation: ListView.Horizontal
interactive: true // disable manual pageChange
snapMode: ListView.SnapOneItem // while moving to right, finish move
highlightRangeMode: ListView.StrictlyEnforceRange // mouse gesture make currentIndex change
highlightMoveDuration: 400 // speed up pages change (swap)
model: ObjectModel {
/* First page with login capabilities */
Login {
id: login_module
width: root_layout.width
height: root_layout.height
}
}
}
}
/* Private function definition*/
function init_database()
{
var database = Storage.LocalStorage.openDatabaseSync(Style.databaseName, Style.databaseVersion, Style.databaseDescr, Style.databaseSize);
smonDatabase.startDatabase(Style.databaseName);
}
Component.onCompleted: {
init_database();
}
}
Here is base of Login page
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.2
import "../"
import "./common"
Rectangle {
id: login_page
// why parent.width is not set ?
anchors.fill: parent
//width: parent.width
//Layout.fillWidth: true
property string credentials_title: qsTr("Přístupové údaje")
property string available_devices: qsTr("Dostupné servery")
property string identity_title: qsTr("Identita")
property string password_title: qsTr("Heslo")
property string domain_title: qsTr("Doména")
property string infoLine_title: qsTr("Zapamatovat přihlašovací údaje")
property string username_title: qsTr("Jméno");
Component.onCompleted: {
console.log("Login width: " + login_page.width);
control.cancelEnabled = false
}
ColumnLayout{
id: navigation
spacing: Style.spacing
anchors.left: parent.left
anchors.leftMargin: Style.defaultAnchors
Layout.fillWidth: true
anchors.fill: parent
width: parent.width
Text {
id: title
//anchors.top: parent.top
//anchors.left: parent.left
font.pointSize: Style.fontSizeHeading
text: credentials_title
}
ColumnLayout{
id: navigationLogin
spacing: Style.spacing
anchors.left: parent.left
anchors.leftMargin: Style.defaultAnchors
Layout.fillWidth: true
Layout.bottomMargin: Style.bottomMargin
width: (parent.width - 4*Style.defaultAnchors)
GridLayout {
id: input_login
rowSpacing: Style.denseSpacing
columns: 2
columnSpacing: Style.spacing
anchors.left: parent.left
anchors.leftMargin: Style.defaultAnchors
width: 200
Text {
id: user_name
font.pointSize: Style.fontSizeSubHeading
text: username_title
}
SmonComboBox {
id: user
width: parent.width
value: smonRole.user
object: smonRole
prop: "user"
isEditable: true
dataModel: smonRole.userData
selectedIndex: smonRole.userNameSelected
}
Text {
id: password_name
font.pointSize: Style.fontSizeSubHeading
text: password_title
}
SmonTextField {
id: password
width: parent.width
type: "password"
object: smonRole
prop: "pass"
value: smonRole.pass
onEnterPressed: {
user.enabled = false
password.enabled = false
//control.okEnabled = false
control.okEnabled = false
control.cancelEnabled = true
smonRole.save();
smonCommunication.connect();
}
onValueChanged: {
if(password.value !== "")
{
control.okEnabled = true
}
else
{
control.okEnabled = false
}
}
}
}
ColumnLayout {
id: scanning
spacing: Style.denseSpacing
anchors.left: parent.left
//Layout.fillWidth: true
RowLayout {
id: slider_component
Text {
id: scanningHeader
font.pointSize: Style.fontSizeSubHeading
text: qsTr("Perioda vyhledávání zařízení");
}
Text {
id: value
font.pointSize: Style.fontSizeInfo
anchors.left: scanningHeader.right
anchors.leftMargin: Style.defaultAnchors
width: 30
text: slider.value
}
}
Slider {
id: slider
minimumValue: 2
maximumValue: 30
Layout.fillWidth: true
stepSize: 1
value: smonCommunication.scanPeriod
onValueChanged: {
smonCommunication.scanPeriod = slider.value;
}
}
}
SmonControlPanel {
id: control
width: parent.width
okEnabled: smonRole.user != "" && smonRole.pass != ""
okVisible: true
cancelVisible: true
onSignalOk: {
// hide content
user.enabled = false
password.enabled = false
control.okEnabled = false
control.cancelEnabled = true
smonRole.save();
smonCommunication.connect();
}
onSignalCancel: {
// show content again
password.enabled = true
user.enabled = true
//domain.enabled = true
control.cancelEnabled = false
control.okEnabled = true
//smonConnection.logout();
smonCommunication.disconnect();
smonRole.disconnected();
}
}
}
Text {
id: favourite
font.pointSize: Style.fontSizeHeading
text: available_devices
}
ListView{
id: servers
Layout.fillHeight: true
width: parent.width
model: smonCommunication.devicesList
delegate: Rectangle {
id: serverList
height: 80
width: parent.width
ColumnLayout{
Text {
id: serverName
text: modelData.bluetooth_name
}
Text {
id: rssi
text: modelData.bluetooth_rssi
}
}
MouseArea {
id: bt_device
anchors.fill: parent
onClicked: {
if(smonCommunication.btCanConnect === true)
smonCommunication.connect(index);
}
}
}
}
}
MessageDialog {
id: errorDialog
standardButtons: StandardButton.Cancel | StandardButton.OK
visible: false;
informativeText: smonCommunication.errorMessage
onInformativeTextChanged: {
errorDialog.visible = true;
}
}
}
Is there problem on the main page or on the page which is loaded ? Thanks for help...
Your problem lies with the anchors.fill: parent bit in your ObjectModel.
The parent here, is not the ListView, but the ListView's contentItem, which happens to have an implicit width of 100px.
In your minimal example, something like this should work:
model: ObjectModel {
/* First page with login capabilities */
Rectangle{
id: one
//anchors.fill: parent <- parent is not navigation
width: navigation.width
height: 50
color: "red"
}
}
Generally speaking, you should not use the parent property in your delegates.
So, after answers from ddriver and Kevin Krammer (thanks) I made a minimal working example.
I stopped using ColumnLayout, and set everything as best as I can.
Here is the code
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
import QtQml.Models 2.1
ApplicationWindow {
id: root_window
title: "Hello world"
visible: true
color: "white"
width: 480
height: 520
Rectangle {
id: smon_user_app_header
height: 50
color: "blue"
width: parent.width
}
ListView {
id: navigation
orientation: ListView.Horizontal
interactive: true // disable manual pageChange
snapMode: ListView.SnapOneItem // while moving to right, finish move
highlightRangeMode: ListView.StrictlyEnforceRange // mouse gesture make currentIndex change
highlightMoveDuration: 400 // speed up pages change (swap)
anchors.top: smon_user_app_header.bottom
anchors.bottom: root_window.bottom
width: parent.width
height: 400
model: ObjectModel {
/* First page with login capabilities */
Rectangle{
id: one
anchors.fill: parent
color: "red"
}
}
}
}
And here is how it looks
I have a component named Tile in Tile.qml, which I want to create by a Repeater. Tile.qml is as follows:
import QtQuick 2.0
Rectangle {
id: tile
property string tileLabel: label.text
property int tileSize: height
width: 50
height: tileSize
color: "green"
border.color: Qt.lighter(color)
anchors.bottom: parent.bottom
Text {
id: label
color: "white";
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.bottom
text: tileLabel
}
}
And my main.qml is as follows:
import QtQuick 2.0
Rectangle {
id: root
width: 552; height: 300
color: "#3C3C3C"
border.color: Qt.lighter(color)
Row {
id: tilesRow
anchors.margins: 8
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: 4
Repeater {
id: repeater
model: 10
delegate: Tile {
tileSize: Math.random() * 100 + 1
tileLabel: tileSize
}
}
}
Rectangle {
id: button
width: 100
height: 30
color: "gray"
focus: true
Text {
anchors.centerIn: parent
color: "white"
text: "Button"
}
MouseArea {
hoverEnabled: true
anchors.fill: parent
onEntered: { button.color = Qt.lighter("blue")}
onExited: { button.color = "gray" }
onPressed: { button.color = "blue" }
onReleased: { button.color = Qt.lighter("blue") }
onClicked: func()
}
}
}
I need to sort the tiles when the button is clicked so that the tiles are in ascending order by their labels. I can access the labels of the tiles using repeater.itemAt(i).tileSize. How can I animate the movement of tiles as they are moved/swapped?
Small example:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
width: 800
height: 600
Row {
anchors.centerIn: parent
property var word: ['H','e','l','l','o','!']
id: row
Repeater {
id: repeater
model: row.word.length
delegate: Rectangle {
id: delegate;
width: 100
height: 100
property int pos
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1);
Text {
anchors.centerIn: parent
font.pixelSize: 36
color: "white"
text: row.word[index]
}
Behavior on x {
ParallelAnimation {
PropertyAnimation {
duration: 500
easing.type: Easing.InOutBack
}
SequentialAnimation {
PropertyAnimation {
target: delegate
property: "y"
from: 0
to: delegate.pos == 1 ? 20 : -20
duration: 250
}
PropertyAnimation {
target: delegate
property: "y"
from: delegate.pos == 1 ? 20 : -20
to: 0
duration: 250
}
}
}
}
Behavior on rotation {
RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 300
}
}
}
}
}
Timer {
interval: 1000
running: true
repeat: true
onTriggered: {
var element1 = repeater.itemAt(Math.round(Math.random() * (repeater.count - 1)));
var element2 = repeater.itemAt(Math.round(Math.random() * (repeater.count - 1)));
if(element1 === element2) {
element1.rotation = element1.rotation + 90;
} else {
element1.pos = 1;
element2.pos = 2;
var temp = element1.x;
element1.x = element2.x;
element2.x = temp;
}
}
}
}
I'm working on a QML StackView that starts with a list of items to select from. Once selected I want to transition _.push(...) to a input form which has larger dimensions than the initialItem.
The only way I have trial-and-errored my way into a situation that works is by making the form Item a nested borderless window.
Q1. A nested window can't be the right type of concept to use for this... right ? there must be another way to do it. What is the right way ?
Q2. My goal after this is to have a transition animation that grows or shrinks between stacks of different sizes. Advice that doesn't preclude that would be best.
code
Main.qml :
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
ApplicationWindow {
property int itemHeight: 30
property int cornerRadius : 5
visible: true
color: "transparent"
flags: Qt.FramelessWindowHint
ListModel {
id: searchFacets
ListElement {
title: "Topics"
page: "content/TopicSearch.qml"
}
// ListElement {
// title: "Domains"
// }
}
StackView {
id: stackView
focus: true
initialItem: SearchFacets {
id: facets
}
delegate: StackViewDelegate {
pushTransition: StackViewTransition {
PropertyAnimation {
target: enterItem
property: "opacity"
from: 0
to: 1
}
}
}
}
}
Initial Item:
import QtQuick 2.3
Item {
height : listView.count * itemHeight
ListView {
id: listView
model: searchFacets
anchors.fill: parent
focus: true
highlightFollowsCurrentItem: false
highlight: Rectangle {
width: parent.width
height: itemHeight
radius : cornerRadius
color: "green"
opacity: 0.5
z:2
x: listView.currentItem.x;
y: listView.currentItem.y
Behavior on y {
SpringAnimation {
spring: 60
damping: 1.0
}
}
}
delegate: Component {
Item {
width: parent.width
height : itemHeight
Rectangle {
anchors.fill: parent
color: "#212126"
radius: cornerRadius
z:0
border.width: 2
border.color : "white"
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
console.log("clicked index: " + index)
listView.currentIndex = index
// listView.forceActiveFocus()
stackView.push(Qt.resolvedUrl(page))
}
}
Text {
text: title
font.pixelSize: 24
font.bold: true
z:1
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color: "white"
antialiasing: true
}
}
}
}
}
Input Form:
import QtQuick 2.3
import QtQuick.Window 2.0
Item {
Window {
width: 400
height: 400
visible: true
flags: Qt.FramelessWindowHint
Rectangle {
anchors.fill: parent
visible: true
color: "red"
}
}
}
One possible solution is to update the size of the dimensions of the StackView in the click handler that causes the transition. I do not know if that causes any problems with animating the transition.
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
console.log("clicked index: " + index)
listView.currentIndex = index
var component = Qt.createComponent(page)
var res = component.createObject(stackView)
stackView.height = res.height
stackView.width = res.width
stackView.push(res)
}
}