How to move a cell in GridLayout - QML? - qt

Considering the documentation of GridLayout, here is what I have tried:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window
{
visible: true
MainForm
{
GridLayout {
id: gridLayout
columns: 3
height: 100
width: 100
property int oneRow: 0
property int oneCol: 0
Rectangle { id: one; Layout.row :gridLayout.oneRow; Layout.column: gridLayout.oneCol;
height: 50; width: 50; color: "brown"}
Rectangle { height: 50; width: 50; color: "red" }
Rectangle { height: 50; width: 50; color: "blue"}
Rectangle { height: 50; width: 50; color: "green"}
Rectangle { height: 50; width: 50; color: "yellow"}
}
Component.onCompleted:
{
gridLayout.oneRow = 2
gridLayout.oneCol = 2
}
}
}
If I comment out this code from Component.onCompleted,
gridLayout.oneRow = 2
gridLayout.oneCol = 2
I get:
Whereas I want that brown square to "move to" the second row's last column.
So, I wrote:
gridLayout.oneRow = 1
gridLayout.oneCol = 2
in Component.onCompleted.
but then, I got the following:
which is NOT what I wanted.
Please help.

If you wish to change the cell number of some item in the GridLayout, then you need to assign the initial row number and column number to all the elements _yourself_, and then change the position of the desired item dynamically as shown below:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window
{
visible: true
MainForm
{
GridLayout {
id: gridLayout
height: 100
width: 100
property int oneRow: 0
property int oneCol: 0
Rectangle { id: one;
Layout.row :gridLayout.oneRow; Layout.column: gridLayout.oneCol;
height: 50; width: 50; color: "brown"}
property int twoRow: 0
property int twoCol: 1
Rectangle { id: two;
Layout.row :gridLayout.twoRow; Layout.column: gridLayout.twoCol;
height: 50; width: 50; color: "red" }
property int threeRow: 0
property int threeCol: 2
Rectangle { id: three;
Layout.row :gridLayout.threeRow; Layout.column: gridLayout.threeCol;
height: 50; width: 50; color: "blue"}
property int fourRow: 1
property int fourCol: 0
Rectangle { id: four;
Layout.row :gridLayout.fourRow; Layout.column: gridLayout.fourCol;
height: 50; width: 50; color: "green"}
property int fiveRow: 1
property int fiveCol: 1
Rectangle { id: five;
Layout.row :gridLayout.fiveRow; Layout.column: gridLayout.fiveCol;
height: 50; width: 50; color: "yellow"}
}
Component.onCompleted:
{
gridLayout.oneRow = 1
gridLayout.oneCol = 2
}
}
}
Now, as you can see the following code in Component.onCompleted moves the brown rectangle to the 2nd row's 3rd column.
Component.onCompleted:
{
gridLayout.oneRow = 1
gridLayout.oneCol = 2
}

Related

Set correct size for custom QML slider

I'm trying to create a reusable slider. I'm having trouble to set implicit sizes correctly so that the CustomSlider includes the Slider and the Label. I would like to have a implicit size specified, but let the user set a width for the slider itself.
I tried using childrenRect but that gives me a binding loop error.
How can I have the yellow background span across all the components: the slider and the green label?
Currently:
Would like:
CustomSlider.qml
Item {
id: root
property int startval: 0
property int endval: 20
property int sliderWidth: 200
// This results in binding loop
//implicitHeight: childrenRect.height
implicitHeight: control.height + label.height
implicitWidth: sliderWidth
Rectangle {
color: "yellow"
width: root.width
height: root.height
}
Slider {
id: control
stepSize: 1
anchors.centerIn: parent
snapMode: Slider.SnapOnRelease
width: root.sliderWidth
from: root.startval
to: root.endval
handle: Rectangle {
id: handleId
x: control.visualPosition * (control.width - width)
y: (control.height - height) / 2
width: 20
height: 20
radius: 20
color: "gray"
}
background: Rectangle {
y: (control.height - height) / 2
height: 4
radius: 2
color: "green"
Rectangle {
width: control.visualPosition * parent.width
height: parent.height
color: "red"
radius: 2
}
}
}
Label {
id: label
width: 20
height: 20
text: control.value
font.pixelSize: 15
color: "black"
x: handleId.x + control.x
y: handleId.y - 20
Rectangle {
color: "green"
anchors.fill: parent
opacity: 0.3
}
}
}
Main.qml
CustomSlider {
anchors.centerIn: parent
startval: 0
endval: 10
//sliderWidth: 100
}
I usually set the Width and height based on the parent.
I set sliderWidth: parent.width/2.0 and for your green label I add TextMetrics and calculate its width based on the text that it wants to show.
import QtQuick 2.15
import QtQuick.Controls 2.15
Item {
id: root
property int startval: 0
property int endval: 0
property int sliderWidth: parent.width/2.0
// This results in binding loop
//implicitHeight: childrenRect.height
implicitHeight: control.height + label.height
implicitWidth: sliderWidth
Rectangle {
color: "yellow"
width: root.width
height: root.height
}
Slider {
id: control
stepSize: 1
anchors.centerIn: parent
snapMode: Slider.SnapOnRelease
width: root.sliderWidth
from: root.startval
to: root.endval
handle: Rectangle {
id: handleId
x: control.visualPosition * (control.width - width)
y: (control.height - height) / 2
width: 20
height: 20
radius: 20
color: "gray"
}
background: Rectangle {
y: (control.height - height) / 2
height: 4
radius: 2
color: "green"
Rectangle {
width: control.visualPosition * parent.width
height: parent.height
color: "red"
radius: 2
}
}
}
Label {
id: label
width: t_metrics.tightBoundingRect.width +4
height: t_metrics.tightBoundingRect.height +7
text: control.value
font.pixelSize: 15
color: "black"
x: handleId.x + control.x
y: handleId.y - 20
Rectangle {
color: "green"
anchors.fill: parent
opacity: 0.3
}
}
TextMetrics {
id: t_metrics
text: control.value.toString()
}
}
Updated:
in CustomSlider.qml , changed it to this code:
import QtQuick 2.15
import QtQuick.Controls 2.15
Item {
id: root
property int startval: 0
property int endval: 0
property int sliderWidth: parent.width/2.0
implicitHeight: control.height + label.height
implicitWidth: sliderWidth
Rectangle {
color: "yellow"
width: root.width +10
height: root.height +control.height + label.height + t_metrics.tightBoundingRect.height +7
Slider {
id: control
stepSize: 1
anchors.centerIn: parent
snapMode: Slider.SnapOnRelease
width: root.sliderWidth
from: root.startval
to: root.endval
handle: Rectangle {
id: handleId
x: control.visualPosition * (control.width - width)
y: (control.height - height) / 2
width: 20
height: 20
radius: 20
color: "gray"
}
background: Rectangle {
y: (control.height - height) / 2
height: 4
radius: 2
color: "green"
Rectangle {
width: control.visualPosition * parent.width
height: parent.height
color: "red"
radius: 2
}
}
}
Label {
id: label
width: t_metrics.tightBoundingRect.width +4
height: t_metrics.tightBoundingRect.height +7
text: control.value
font.pixelSize: 15
color: "black"
x: handleId.x + control.x
y: handleId.y /2 + 4
Rectangle {
color: "green"
anchors.fill: parent
opacity: 0.3
}
}
TextMetrics {
id: t_metrics
text: control.value.toString()
}
}
}
This makes your text label shows inside the yellow rectangle and in main.qml I add one Row with labels and spinboxes for the test.
If you want that user to set sliderWidth or startval and endval and then show him Slider you need to create that object dynamically.
and if you want to be displayed in the Column and don't overlap you need ColumnLayout.
If you want to scroll them then you need to use ScrollView.
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Window {
width: 660
height: 480
visible: true
title: qsTr("Hello World")
ScrollView {
width: parent.width
height : parent.height
contentWidth: slidersColumn.width
contentHeight: slidersColumn.height
clip : true
ColumnLayout {
id: slidersColumn
anchors.fill: parent
spacing:50
}
}
function addSlider(sliderWidth,startval,endval) {
var obj = Qt.createComponent("CustomSlider.qml");
var slider = obj.createObject(slidersColumn);
slider.startval=startval;
slider.endval=endval;
slider.sliderWidth=sliderWidth;
}
Row
{
x: 0
y: 5
width: parent.width
height: 30
Label {
id: lbl_slider_width
text: qsTr(" Slider width ")
}
SpinBox {
id: spinBox_slider_width
editable: true
from:0
to:parent.width
}
Label {
id: lbl_startval
text: qsTr(" Startval ")
}
SpinBox {
id: spinBox_startval
editable: true
from:0
to:parent.width
}
Label {
id: lbl_endval
text: qsTr(" Endval ")
}
SpinBox {
id: spinBox_endval
editable: true
from:0
to:parent.width
}
Button {
id: button
text: qsTr(" Create Slider")
onClicked:
{
addSlider(spinBox_slider_width.value,spinBox_startval.value,spinBox_endval.value)
}
}
}
}
The result is:

How do I create a ripple button effect in QML?

I'm using QtQuick/QML and I want to create a ripple effect when I click on a button. I do know that this is available in Material Style, but I think it's an inherent property when you change the theme and I don't want to change anything else in my project.
Is there a way to add ONLY the ripple effect onto my button, and change nothing else? If so, how do I do it?
As Kostia Hvorov said, QtQuick.Controls.Material.impl.Ripple is the easiest way to go.
I would like to add my trick to handle rectangular background with radius:
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.Material.impl 2.12
import QtGraphicalEffects 1.12
Column
{
spacing: 20
Button
{
anchors.horizontalCenter: parent.horizontalCenter
id: button
text: "ripple demo"
}
Ripple {
id: ripple
anchors.horizontalCenter: parent.horizontalCenter
clipRadius: 4
width: 200
height: 64
pressed: button.pressed
active: button.down || button.visualFocus || button.hovered
color: "#20FFFFFF"
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle
{
width: ripple.width
height: ripple.height
radius: 4
}
}
}
}
Try it Online
Easiest way to do it - using Ripple from QtQuick.Controls.Material.impl
So just add Ripple to your background Rect:
Ripple {
clipRadius: height
width: parent.width
height: parent.height
pressed: control.pressed
anchor: control
active: control.down || control.visualFocus || control.hovered
color: control.flat && control.highlighted ? control.Material.highlightedRippleColor : control.Material.rippleColor
}
You can replace "control.Material.rippleColor" or/and "control.Material.highlightedRippleColor" to any color and get any ripple color effect.
But there is one problem, it will work only with rectangular background(without round) otherwise it will be looking bad.
I have made this with some PropertyAnimation. Here is how:
import QtQuick 2.15
import QtQuick.Controls 2.15
Button {
id: control
opacity: enabled ? 1.0 : 0.2
property int tripleWidth: width * 3
background: Rectangle {
border.width: 1
border.color: "black"
radius: 3
color: "white"
clip: true
Rectangle {
id: ripple
property int diameter: 0
property int pressX: 0
property int pressY: 0
x: pressX - radius
y: pressY - radius
color: "green"
radius: diameter / 2
width: diameter
height: diameter
opacity: 1 - diameter / control.tripleWidth
function animate(x, y, size) {
pressX = x
pressY = y
diameter = size
}
Behavior on diameter {
PropertyAnimation {
duration: 200
onRunningChanged: {
if(!running) {
duration = 0;
ripple.diameter = 0;
duration = 200;
}
}
}
}
}
}
onClicked: {
ripple.animate(pressX, pressY, control.tripleWidth)
}
contentItem: Item {
implicitWidth: txt.implicitWidth
implicitHeight: 20
Text {
id: txt
anchors.centerIn: parent
text: control.text
}
}
}
I Edit last Answer and its work.. Here is How:
import QtQuick 2.5
import QtQuick.Window 2.5
import QtQuick.Controls 2.5
RoundButton {
id: control
width: 93
height: 39
property int tripleWidth: width * 3
background: Rectangle {
border.width: 1
border.color: "black"
radius: 3
color: "white"
clip: true
Rectangle {
id: ripple
property int diameter: 0
property int pressX: 0
property int pressY: 0
x: pressX - radius
y: pressY - radius
color: "green"
radius: diameter / 2
width: diameter
height: diameter
opacity: 1
function animate(x, y, size) {
pressX = x
pressY = y
diameter = size
}
Behavior on diameter {
PropertyAnimation {
duration: 300
}
}
}
}
onHoveredChanged: {
ripple.opacity = 0
ripple.diameter = 0
}
onPressed: {
ripple.opacity = 0.8
ripple.animate(pressX, pressY, control.tripleWidth)
}
Timer {
id: timer
}
contentItem: Item {
implicitWidth: txt.implicitWidth
implicitHeight: 20
Text {
id: txt
font.pointSize: 15
anchors.centerIn: parent
}
}
onClicked: {
function delay(delayTime, cb) {
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.start();
}
delay(10, function() {
ripple.opacity = 0
ripple.diameter = 0
})
}
}
Try it....

Animation of y position for a Column centerd in parent

I have a Column with three elements and one element can change its visibility if the user hits the spacebar. To make the visibility change look smoothly I can add a move transition:
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
Column {
spacing: 2
Rectangle { color: "red"; width: 50; height: 50 }
Rectangle { id: greenRect; color: "green"; width: 20; height: 50 }
Rectangle { color: "blue"; width: 50; height: 20 }
move: Transition {
NumberAnimation { properties: "x,y"; duration: 1000 }
}
focus: true
Keys.onSpacePressed: greenRect.visible = !greenRect.visible
}
}
This works. But if I center the Column in its parent the visibility change also results in a new height for the Columnand therefore also to a new y position.
Now I don't want the Column to 'jump' to its new position but also to move smoothly. So I added this to the Column:
anchors.centerIn: parent
Behavior on y {
NumberAnimation { duration: 1000 }
}
But the y position change is still not animated. How to achieve this?
I added another Item element which holds the Column. This allows you to set a Behavior on the item's height property and is what you're looking for:
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
width: 640
height: 480
visible: true
Item {
id: container
width: col.width
height: col.height
anchors.centerIn: parent
property int animationDuration: 200
Behavior on height {
PropertyAnimation {
duration: container.animationDuration
}
}
Column {
id: col
spacing: 2
focus: true
Rectangle { color: "red"; width: 50; height: 50 }
Rectangle { id: greenRect; color: "green"; width: 20; height: 50 }
Rectangle { color: "blue"; width: 50; height: 20 }
move: Transition {
NumberAnimation { properties: "x,y"; duration: container.animationDuration }
}
Keys.onSpacePressed: greenRect.visible = !greenRect.visible
}
}
}
Hope this helps!
Property changes induced by anchors don't seem to trigger Behaviours.
As a workaround, manually center the Column:
import QtQuick 2.2
Rectangle {
width: 640
height: 480
Column {
spacing: 2
anchors.horizontalCenter: parent.horizontalCenter
y: (parent.height - height) / 2
Behavior on y {
NumberAnimation { duration: 1000 }
}
Rectangle { color: "red"; width: 50; height: 50 }
Rectangle { id: greenRect; color: "green"; width: 20; height: 50 }
Rectangle { color: "blue"; width: 50; height: 20 }
move: Transition {
NumberAnimation { properties: "x,y"; duration: 1000 }
}
focus: true
Keys.onSpacePressed: greenRect.visible = !greenRect.visible
}
}

Better way to reparent visual items in QML

It would seem that in the design of QML user reparent was not really "envisioned", because even though it is possible, it involves creating and changing states, which is just not convenient to add to each and every item.
import QtQuick 1.0
Item {
width: 200; height: 100
Rectangle {
id: redRect
width: 100; height: 100
color: "red"
}
Rectangle {
id: blueRect
x: redRect.width
width: 50; height: 50
color: "blue"
states: State {
name: "reparented"
ParentChange { target: blueRect; parent: redRect; x: 10; y: 10 }
}
MouseArea { anchors.fill: parent; onClicked: blueRect.state = "reparented" }
}
}
I was wondering if there is a more elegant way to reparent items without polluting items with unnecessary states?
not certain if you need to use QtQuick 1.0, but with 2.0 this also works and is imo more straight forward.
import QtQuick 2.0
Item {
width: 200; height: 100
Rectangle {
id: redRect
width: 100; height: 100
color: "red"
}
Rectangle {
id: blueRect
x: redRect.width
width: 50; height: 50
color: "blue"
MouseArea { anchors.fill: parent; onClicked:
{ blueRect.parent = redRect; blueRect.x = 10; blueRect.y = 10 }
}
}
}

QML: How to move items within a grid

I have a 4x4 grid and I want to associate arrow key presses with the movement of items within the grid. How does one do that?
Here is a sample QML:
import QtQuick 1.1
Rectangle {
id: main;
width: 500; height: 500;
color: "darkgreen";
property int emptyBlock: 16;
Grid {
id: grid16;
x: 5; y: 5;
width: 490; height: 490;
rows: 4; columns: 4; spacing: 5;
Repeater {
model: 1;
Rectangle {
width: 118; height: 118; color: "darkblue";
}
}
}
Keys.onRightPressed: pressRight();
function pressRight() {
console.log("Left key pressed");
}
focus: true;
}
Update 1: Thanks to sebasgo and alexisdm for the answers. If moving within a grid is not that easy why we have the move transition property [http://qt-project.org/doc/qt-4.8/qml-grid.html#move-prop]
You'd better use a GridView Item instead of your Grid approach.
This way you can use it's currentIndex property to choose which item to move like this:
import QtQuick 1.1
Rectangle {
id: main;
width: 500; height: 500;
color: "darkgreen";
property int emptyBlock: 16;
GridView {
id: grid16;
x: 5; y: 5;
width: 490; height: 490;
model: gridModel
delegate: Component{
Rectangle {
width: 118; height: 118; color: "darkblue";
Text {
anchors.centerIn: parent
font.pixelSize: 20
text: value
}
}
}
}
ListModel {
id: gridModel
ListElement {value: 1}
ListElement {value: 2}
ListElement {value: 3}
ListElement {value: 4}
}
Keys.onRightPressed: {
gridModel.move(grid16.currentIndex, grid16.currentIndex+1, 1)
}
Keys.onLeftPressed: {
gridModel.move(grid16.currentIndex, grid16.currentIndex-1, 1)
}
focus: true;
}
Grids give you no way to manipulate the position of the contained items directly. Instead their position is directly derived from the physically order of the child items of the grid. There is no easy way to to manipulate child items in QML dynamically, so I think you should abandon the Grid item and specify the position of the child items explicitly with the x and y properties. Applied to your code this could look like:
Rectangle {
id: main;
width: 500; height: 500;
color: "darkgreen";
Item {
x: 5; y: 5;
width: 490; height: 490;
Repeater {
id: pieces
model: 1;
Rectangle {
property int column: 0
property int row: 0
x: column * 123
y: row * 123
width: 118; height: 118; color: "darkblue";
}
}
}
Keys.onRightPressed: pressRight();
function pressRight() {
console.log("Left key pressed");
pieces.itemAt(0).column++
}
focus: true;
}
Update 1:
Grids (in combination with a Repeater) can be used to visualize models, e.g., a XmlListModel item or an QAbstractItemModel descendent.
With move property it's easy to react to layout changes in the model (if an entry is removed/added) in an animated way. Still, the items in the Grid are laid out strictly in the order of the entries of the model.
So if you want have manual control over the position of your items, even in cellular layout, use of a Grid is not advisable.
You can change the number of items before the item you want to move to change its position:
import QtQuick 1.1
Rectangle {
id: main;
width: 500; height: 500;
color: "darkgreen";
property int emptyBlock: 16;
property int posX: 0;
property int posY: 0;
Grid {
id: grid;
x: 5; y: 5;
width: 490; height: 490;
rows: 4; columns: 4; spacing: 5;
Repeater {
id: beforeTheItem
model: main.posX + parent.columns * main.posY
Rectangle {
width: 118; height: 118; color: "transparent";
}
}
Rectangle {
id:theItem
width: 118; height: 118; color: "darkblue";
}
}
Keys.onPressed: {
// To avoid flickering, the item is hidden before the change
// and made visible again after
theItem.visible = false;
switch(event.key) {
case Qt.Key_Left: if(posX > 0) posX--;
break;
case Qt.Key_Right: if(posX < grid.columns - 1) posX++;
break;
case Qt.Key_Up: if(posY > 0) posY--;
break;
case Qt.Key_Down: if(posY < grid.rows - 1) posY++;
break;
}
theItem.visible = true;
}
focus: true;
}
Now, by using Qt 5.1 or above and GridLayout you can move your items without hassle:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window
{
visible: true
MainForm
{
GridLayout {
id: gridLayout
columns: 3
property int oneRow: 0
property int oneCol: 0
Text { id: one; Layout.row :grid.oneRow; Layout.column: grid.oneCol; text: "My"; font.bold: true; }
Text { text: "name"; color: "red" }
Text { text: "is"; font.underline: true }
Text { text: "not"; font.pixelSize: 20 }
Text { text: "Ravan"; font.strikeout: true }
}
Component.onCompleted:
{
gridLayout.oneRow = 1
gridLayout.oneCol = 2
}
}
}
The GridView is a very confusing monster. It just populates one row from a given model, which leads to confusion since it is called GRID. But it can still be used as a fixed size grid, as I show in the example below. A single square can be moved with arrow keys on a 4x4 sized grid.
GridView {
id: grid16;
anchors.fill: parent
cellWidth: parent.width / 2
cellHeight: parent.height / 2
model: gridModel
delegate:
Rectangle {
Component.onCompleted: if( index >= 1 ) visible = false
width: grid16.cellWidth ; height: grid16.cellHeight ; color: "yellow";
Text {
anchors.centerIn: parent
font.pixelSize: 20
text: value
}
}
move: Transition {
NumberAnimation { properties: "x,y"; duration: 1000 }
}
}
ListModel {
id: gridModel
ListElement {value: 1}
//Necessary, otherwise the grid will have the dimension 1x1
ListElement {value: 2}
ListElement {value: 3}
ListElement {value: 4}
}
Keys.onRightPressed: { gridModel.move(grid16.currentIndex, grid16.currentIndex+1, 1) }
Keys.onLeftPressed: { gridModel.move(grid16.currentIndex, grid16.currentIndex-1, 1) }
Keys.onUpPressed: { gridModel.move(grid16.currentIndex, grid16.currentIndex-2, 1) }
Keys.onDownPressed: { gridModel.move(grid16.currentIndex, grid16.currentIndex+2, 1) }
focus: true;
}

Resources