QML How to paint different to grabToImage() - qt

Is there any possible way to make grabToImage() buffer different from what's displayed in GUI? I'm using ChartView without legend in GUI and want to export it to PNG with legend. So I try:
chartView.legend.visible = true
chartView.update()
chartView.grabToImage(function(result) {
console.log("grabbed")
var path = filename.toString().replace(/^(file:\/{2})/,"");
console.log(path + result.saveToFile(path));
chartView.legend.visible = false
update();
});
But both those updates happen only after the control comes out of this function, so I don't get legend drawn in PNG. Also I would like the appearance of legend to be unnoticable to user. Are there any ways to do that in QML?

I am unsure, whether I got you quite right.
My suggestion is, to not grab the Item you display, but a copy of it, that you then modify as you like.
Copy here does not mean, that you have the same object twice, but that you render it twice by using a ShaderEffectSource. Before you grab this ShaderEffectSource to image, you can add anything you like.
In my example I display a simple Rectangle with a nice gradient. What I save is the same Rectangle that is extended by the Text 'I am legend'. The user won't see this text apearing in the view at any time.
Rectangle {
id: commonView
width: 200
height: 200
gradient: Gradient {
GradientStop { position: 0; color: 'steelblue' }
GradientStop { position: 1; color: 'orange' }
}
MouseArea {
anchors.fill: parent
// don't grab the rectangle itself.
onClicked: legendView.grabToImage(function(result) {
console.log(result.saveToFile("something.png"));
});
}
}
ShaderEffectSource {
id: legendView
visible: false // Does not need to be shown.
sourceItem: commonView
width: 200
height: 200
Text {
anchors {
right: parent.right
bottom: parent.bottom
}
text: 'I am legend'
}
}
You might optimize the performance by only having the ShaderEffectSource active or even created when needed.
You might use the ShaderEffectSource.live-property to disable updating of it. Then use scheduleUpdate() to trigger the update.
This might look like this:
Rectangle {
id: commonView
width: 200
height: 200
gradient: Gradient {
GradientStop { position: 0; color: 'steelblue' }
GradientStop { id: gs1; position: 1; color: 'orange' }
}
MouseArea {
anchors.fill: parent
onClicked: {
gs1.position -= 0.1
legendView.save()
}
}
}
ShaderEffectSource {
id: legendView
y: 200
visible: false // Do not render it (will only be rendered when called grapToImage()
sourceItem: commonView
width: 200
height: 200
live: false // Will only be updated, when explicitly called for
function save() {
console.log('Schedule Save')
scheduleUpdate() // explicitly update. grabToImage() will force rendering afterwards.
legendView.grabToImage(function(result) {
console.log(result.saveToFile("something" +gs1.position.toFixed(1) + ".png"));
})
}
// Put additional stuff on it.
Text {
anchors {
right: parent.right
bottom: parent.bottom
}
text: 'I am legend!'
}
}

Related

QML Rectangle Hide overlapping Objects

I'm trying to achieve a simple Sliding Bar in QML. Here's a prerequisite I have to follow: no SVG images ( for some BR I cannot discuss here ).
Rectangle {
id: backgroundPower
anchors.fill: parent
color: "#a2aaae"
radius: 50
MouseArea {
id: progressArea
anchors.fill: parent
onPositionChanged: {
updateValue(progressArea.mouseX)
}
onReleased: {
updateValue(progressArea.mouseX)
}
onClicked: updateValue(progressArea.mouseX)
}
Rectangle {
id: powerImage
width: calculateWidth()
radius: 100
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
LinearGradient {
id: defaultGradient
anchors.fill: parent
source: powerImage
start: Qt.point(0, 0)
end: Qt.point(0, powerImage.height)
gradient: Gradient {
GradientStop { position: 0.0; color: "#46c0e4" }
GradientStop { position: 0.50; color: "#0583ca" }
GradientStop { position: 0.95; color: "#fbffff" }
GradientStop { position: 1.0; color: "#fbffff" }
}
}
Here's what I obtain with this code when the bar is at 100%:
When I update the value ( mouse area events ) I change the width of the
powerImage with the function:
var maxWidth = powerImage.parent.width
var currentWidth = (maxWidth * barPercentage) / 100
if (currentWidth >= maxWidth){
currentWidth = maxWidth
}
PROBLEM
When the percentage of the bar reaches a low value ( like 10% ) I cannot fit the bar into the background, like this:
you can see how the bar doesn't fin anymore.
I know the problem is linked to the rectangle ( powerImage ) and its radius. But I don't know how to fix this.
Clearly with a SVG it's really simple but in this case, I cannot use this.
Is there a way to show the bar when it's only inside the background rectangle?
Unfortunately it is not possible to use clip: true on non-rectangular regions.
But following this answer, you can use an OpacityMask:
Rectangle {
id: backgroundPower
anchors.fill: parent
color: "#a2aaae"
radius: 50
MouseArea {
// ...
}
}
Item {
anchors.fill: backgroundPower
layer.enabled: true
layer.effect: OpacityMask {
maskSource: backgroundPower
}
Rectangle {
id: powerImage
// ...
}
}
Result:
But as mentionned in the answer:
But using it will be GPU consuming task as the inner item and mask have to be drawn on buffer first and then redrawn on window, so not very good for old mobile or weak embedded devices.

How to implement curved scrollview in qml?

How to get the look of curved Scroll bar/scroll view as shown below in QML with Label or TextArea?
Basically this application is not a touch application.
Environment, Qt 5.7.0 in Linux.
You can use PathInterpolator from Controls.2. The example below is some Slider modification, you can adopt it for your needs:
import QtQuick 2.9
import QtQuick.Controls 2.2
ApplicationWindow {
id: mainWindow
visible: true
width: 400
height: 400
Path {
id: myPath
startX: 0; startY: 20
PathCurve { x: 100; y: 40 }
PathCurve { x: 200; y: 10 }
PathCurve { x: 300; y: 40 }
}
Slider {
id: control
width: 300
height: 50
anchors.centerIn: parent
background: Rectangle {
anchors.fill: parent
color: "orange"
Canvas {
anchors.fill: parent
contextType: "2d"
onPaint: {
context.strokeStyle = "MediumPurple";
context.path = myPath;
context.stroke();
}
}
PathInterpolator {
id: motionPath
path: myPath
progress: control.visualPosition
}
}
handle: Rectangle {
width: 30
height: 30
radius: 15
color: "DodgerBlue"
x: motionPath.x - 15
y: motionPath.y - 15
}
}
}
You can use a Flickable to have your view. To this Flickable you attatch a ScrollBar which you can style.
To style this ScrollBar is a bit tricky, for some of its properties are bullshit.
The position-property, which is documented as
This property holds the position of the scroll bar, scaled to 0.0 - 1.0.
will never reach 1.0 unless, the handles size is 0. You don't really have the ability to set the size of the handle, though. It will be automatically resized. So if you don't want to have a handle that fills the width of the ScrollBar entirely, you need to use a Item as a base and add a the visual inside this, so you have the sovereignity again.
All together, it might look like this:
Flickable {
anchors.fill: parent
contentWidth: width
contentHeight: mainWindow.height * 10
Rectangle {
width: 640
height: mainWindow.height * 10
gradient: Gradient {
GradientStop { color: 'orchid'; position: 0 }
GradientStop { color: 'orange'; position: 1 }
}
}
ScrollBar.vertical: ScrollBar {
id: scrollBar
width: 50
contentItem: Item {
// This will deal with the bullshit of the position. Imperfect, as I do not consider any margins/paddings
property real normalizedPosition: scrollBar.position * (scrollBar.height / (scrollBar.height - height))
Rectangle {
// Draw the curve by defining a function for x in dependance of the position.
x: Math.sin(Math.PI * parent.normalizedPosition) * 40
width: 10
height: parent.height // I use the default height, so it
// really goes from top to bottom.
// A smaller height means, you should
// also alter the y value to have a
// more natural behavior.
radius: 5
color: 'green'
Text {
text: parent.parent.normalizedPosition
}
}
}
}
}

Display variuos size of ListElements with GridView

Iam relatively new to qml and maybe my attempt is wrong, so please, if you have another idea for solving the issue, please do not hesitate to write it down.
Problem:
I need to Display varoius sizes of ListElemts (which can be dragged and dropped). The main Container is 200px x 180px. The smalles List element is 100px x 60px, the secound size should be 200px x 60px and the biggest should be 200px x 120 px. So I found an Qt example and modified it to my needs, but I have problems regarding the automatic placement of the ListElements.
It works perfect for only small ListElements like here:
Now, if I need a big one, GridView is placing them for Expample like this one:
In this Example, the View is much larger in width like 200px. (gragging the ListElements around may sort them other way, but not proper.)
In the last test, I removed CellWidth and Cell height:
The traget is to display all cells in a 200px x 180px Grid.
As said, maybe my attempt is wrong. Do anyone of you guys have a solution for me?
Here is the modified code. For testing, I commented out things and altered the size property:
Window {
visible: true
width: 480
height: 272
title: qsTr("Hello World")
GridView {
id: root
width: 200; height: 180
cellWidth: 100; cellHeight: 60
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
//! [0]
model: DelegateModel {
//! [0]
id: visualModel
model: ListModel {
id: colorModel
ListElement { color: "blue"; size:1}
ListElement { color: "green"; size:1 }
ListElement { color: "red"; size: 1 }
ListElement { color: "yellow"; size: 1 }
ListElement { color: "orange"; size: 1 }
ListElement { color: "purple"; size: 1 }
}
//! [1]
delegate: MouseArea {
id: delegateRoot
property int visualIndex: DelegateModel.itemsIndex
width:{
switch (model.size) {
case 1:
return 100;
case 2:
return 200;
case 3:
return 200;
}
}
height: {
switch (model.size) {
case 1:
return 60;
case 2:
return 60;
case 3:
return 120;
}
}
drag.target: icon
Rectangle {
id: icon
width:{
switch (model.size) {
case 1:
return 100;
case 2:
return 200;
case 3:
return 200;
}
}
height: {
switch (model.size) {
case 1:
return 60;
case 2:
return 60;
case 3:
return 120;
}
}
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter
}
color: model.color
radius: 3
Drag.active: delegateRoot.drag.active
Drag.source: delegateRoot
Drag.hotSpot.x: icon.width/2
Drag.hotSpot.y: icon.height/2
states: [
State {
when: icon.Drag.active
ParentChange {
target: icon
parent: root
}
AnchorChanges {
target: icon;
anchors.horizontalCenter: undefined;
anchors.verticalCenter: undefined
}
}
]
}
DropArea {
anchors { fill: parent; margins: 0 }
onEntered: visualModel.items.move(drag.source.visualIndex, delegateRoot.visualIndex)
}
}
//! [1]
}
}
}
Thank you in advance for all your thoughts/suggestions!
Edit:
To clear up the questions, here a gif with showd correct rearrangement when drag and drop items:
This Picture shows the the problems I decribed. Some items are overlapping:
This picture is showing the problem with not setted cellWidth and cellHeight of GridView.
As far as I understand, GridView donĀ“t care about the size of its elements. Positioning Elements depends on the cellWith and cellHeight Values. But is it possible to configure GridView the way I need (do not overlap, let items not come outside bonds)?
Or is GradView maybe the wrong attempt to realize this?
If anyone need more information, I will be happy to provide!

QML 'Custom Menu' option moving upwards

I am trying to create a "menu" in QML with custom data in each option
For requirements of my application, I need to show it loading the QML file dynamically (Qt.createComponent). What I need is to show some fixed options in the bottom part, and when clicked the top one, another options appear below this top option, which keeps in the top
A simple example. I have this menu:
Option 4
Option 2
Option 1
And when clicked in Option 4, the menu changes to
Option 4
Option 3
Option 2
Option 1
So Option 4 is moved upwards and Option 3 appears.
I would like to have a 'shadow' around all my menu (I added a DropShadow component for that purpose).
I have this simple test code, where I have a main Rectangle (to be surrounded by the shadow), and 2 Rectangles inside.
Rect1 for the fixed part (Option 1, Option 2), and Rect2 for the 'movable' part (Option 3, Option 4).
Rect2 is behind Rect1 (z: -1), and located to have only Option 4 visible, above Option 2. When clicked Option 4, Rect2 is moved upwards and all options are visible.
To achieve that, I have to update Rect2 visible height, and main window position (y value), since main window height depends on this Rect2 visible height.
I have it working, but it flicks a lot since 2 variables changes ('fixed panel' is moved upwards and back).
I have also tried with a ParallelAnimation for 2 values, but no success.
Any idea to have this menu with a smooth movement?
Main.qml:
import QtQuick 2.0
Rectangle
{
id: window
property variant win: undefined;
Component.onCompleted:
{
var component = Qt.createComponent("TestMenu.qml");
win = component.createObject(window, {"x": 500, "y": 500});
win.show();
}
}
TestMenu.qml:
import QtQuick 2.0
import QtQuick.Window 2.1
import QtGraphicalEffects 1.0
Window {
id: window
flags: Qt.Tool | Qt.FramelessWindowHint
height: panel.height
color: "transparent"
property int radiusShadow: 20
property int iOptionHeight: 30
Rectangle {
id: panel
anchors { centerIn: parent}
height: menu1.height + menu2.heightVisible + 2*radiusShadow
width: window.width - 2*radiusShadow
color: "transparent"
Rectangle {
id: menu1
anchors { bottom: parent.bottom; bottomMargin: radiusShadow }
width: parent.width
height: column1.children.length * iOptionHeight
Column {
id: column1
anchors.fill: parent
Rectangle {
color: "red";
Text { text: qsTr("option 2") }
height: iOptionHeight; width: parent.width
}
Rectangle {
color: "green";
Text { text: qsTr("option 1") }
height: iOptionHeight; width: parent.width
}
}
}
Rectangle {
id: menu2
property int heightVisible: iOptionHeight
anchors { top: parent.top; topMargin: radiusShadow; left: menu1.left }
width: parent.width
height: column2.children.length * iOptionHeight
z: -1
Column {
id: column2
anchors.fill: parent
Rectangle {
id: blue
property bool bOpen: false
color: "blue";
height: iOptionHeight; width: parent.width
Text { text: qsTr("option 4") }
MouseArea {
anchors.fill: parent
onClicked: {
blue.bOpen = !blue.bOpen;
panel.showHideMenu2(blue.bOpen);
}
}
}
Rectangle {
color: "pink";
Text { text: qsTr("option 3") }
height: iOptionHeight; width: parent.width
}
}
}
function showHideMenu2(bShow)
{
if (bShow)
{
window.y -= iOptionHeight
menu2.heightVisible += iOptionHeight;
}
else
{
window.y += iOptionHeight
menu2.heightVisible -= iOptionHeight;
}
}
}
DropShadow {
id: dropShadow
visible: true
anchors.fill: panel
radius: radiusShadow
samples: 24
color: "#40000000"
source: panel
}
}
As a quick answer for your question, you can get what you want using Behavior animation for a property change.
Here, Behavior animation will be used on y (position) change of your window, and for height change of respective rectangles.
Here is the patch for your code I recommend you to apply to see smooth movement:
## -10,6 +10,9 ##
property int radiusShadow: 20
property int iOptionHeight: 30
+ property int animationDuration: 500 // ms
+
+ Behavior on y { NumberAnimation { duration: window.animationDuration } }
Rectangle {
id: panel
## -18,6 +21,7 ##
height: menu1.height + menu2.heightVisible + 2*radiusShadow
width: window.width - 2*radiusShadow
color: "transparent"
+ Behavior on height { NumberAnimation { duration: window.animationDuration } }
Rectangle {
id: menu1
## -25,6 +29,7 ##
anchors { bottom: parent.bottom; bottomMargin: radiusShadow }
width: parent.width
height: column1.children.length * iOptionHeight
+ Behavior on height { NumberAnimation { duration: window.animationDuration } }
Column {
id: column1
## -52,6 +57,8 ##
width: parent.width
height: column2.children.length * iOptionHeight
+ Behavior on height { NumberAnimation { duration: window.animationDuration } }
+
z: -1
Column {
## -64,6 +71,7 ##
color: "blue";
height: iOptionHeight; width: parent.width
Text { text: qsTr("option 4") }
+ Behavior on height { NumberAnimation { duration: window.animationDuration } }
MouseArea {
anchors.fill: parent
## -77,6 +85,7 ##
color: "pink";
Text { text: qsTr("option 3") }
height: iOptionHeight; width: parent.width
+ Behavior on height { NumberAnimation { duration: window.animationDuration } }
}
}
}
## -105,4 +114,4 ##
color: "#40000000"
source: panel
}
-}+}
I have tried with a model and a ListView (code is simpler now), but I don't know where and how insert an 'Animation' or a 'Behaviour' to achieve my target, if it is possible to do it (I have tried several options with no success...)
The expected effect is that the 1st rectangle moves up when the 2nd appears, so the bottom one keeps in its position (at bottom). But the default behaviour when I add an element to the model is that the list increased the height downwards
Maybe anyone could help...
My code:
import QtQuick 2.0
import QtQuick.Controls 1.4
Rectangle {
id: rootItem
width: 400
height: list.height
ListModel {
id: modelRects
ListElement { rectColor: "green" }
ListElement { rectColor: "orange" }
}
ListView {
id: list
height: modelRects.count * 30
model: modelRects
delegate: Rectangle {
id: delegate
height: 30
width: rootItem.width
color: rectColor
MouseArea {
anchors.fill: parent
onClicked: onRectClicked(index);
}
}
}
function onRectClicked(index)
{
if (index == 0)
{
if (modelRects.count == 2)
modelRects.insert(1, {"idRect": 2, "rectColor": "red"});
else
modelRects.remove(1, 1);
}
}
}

Share equally the horizontal space in a QML Row

I need to share equally the horizontal space between all "buttons" in my Row.
I use this code with a Repeater.
Component {
id: buttonComponent
Rectangle {
height: buttonRow.height
width: buttonRow.width / buttonsRepeater.count
color: "#FFDDDD"
Text {
anchors.centerIn: parent
text: model.text
}
}
}
Rectangle {
color: "#DDDDDD"
id: buttonBar
height: 30
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
Row {
id: buttonRow
anchors.fill: parent
Repeater {
id: buttonsRepeater
model: buttonsModel
delegate: buttonComponent
}
}
}
Now, I like to compute the ideal width of the Row such that all my button texts appear correctly.
How can I get this ideal width?
If you don't want to use QtQuick.Layouts as they are not really ready yet, you can use this :
Rectangle {
id: buttonBar;
color: "#DDDDDD";
height: 30;
width: (buttonColumn.width + 20 + buttonRow.spacing) * buttonsRepeater.count;
anchors {
bottom: parent.bottom;
left: parent.left;
}
Column {
id: buttonColumn;
visible: false;
Repeater {
model: buttonsModel;
delegate: Text {
text: model.text;
}
}
}
Row {
id: buttonRow;
anchors.fill: parent;
property real itemWidth : ((width + spacing) / buttonsRepeater.count) - spacing;
Repeater {
id: buttonsRepeater;
model: buttonsModel;
delegate: Component {
id: buttonDelegate;
Rectangle {
height: parent.height;
width: parent.itemWidth;
color: "#FFDDDD";
border.width: 1;
Text {
anchors.centerIn: parent;
text: model.text;
}
}
}
}
}
}
I just used a hidden Column to easily compute max width of Text elements, and added a little padding in the bar width to avoid unspaced text.
The minimum width of a button itself is the implicitWidth property of its Text element.
One solution to your problem might be to add code in the Component.onCompleted handler, i.e. code that is executed after the repeater has created its items, and then sum up these implicitWidth properties of each of the repeater's item (which you can get by using its itemAt(index) function).
These kinds of dynamic layout is a bit cumbersome in QML still, which will get much better in Qt 5.1 with the introduction of Qt Quick Layouts

Resources