Animate Binding Change in Qt - c++

I'm trying to find a way to do a transition on a QML element, when a binding changes. Say you have a Text element, with the text property bound to something. What I want is when the data in the binding changes, the element fades out (Still displaying old data), switches and fades back in with the new data (the actual transition occurring while the element isn't visible.)
I've been searching everywhere for a way to do this but I can figure it out. I've tried using Qt Quick animations within QML, but the data itself changes before the animation runs, leaving the animation unnecessary. I've tried creating a custom QDeclarativeItem object that calls an animation within the QDeclarativeItem::paint() but I can't figure out how to get it to actually run.
I should note here that I know my bindings are working fine as the displayed data changes, I just can't get these animations to run at the proper time.
Here is what I tried with QML:
Text {
id: focusText
text: somedata
Behavior on text {
SequentialAnimation {
NumberAnimation { target: focusText; property: "opacity"; to: 0; duration: 500 }
NumberAnimation { target: focusText; property: "opacity"; to: 1; duration: 500 }
}
}
}
And here is what I tried in implementing a custom QDeclarativeItem:
// PAINTER
void AnimatedBinding::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
// Setup the pen
QPen pen(m_color, 2);
painter->setPen(pen);
painter->setOpacity(this->opacity());
// Draw the item
if (m_bindingType == QString("text")) {
QPropertyAnimation animation(this, "opacity");
animation.setDuration(1000);
animation.setStartValue(1);
if (drawn) {
animation.setStartValue(1);
animation.setEndValue(0);
animation.start();
} else drawn = true;
painter->drawText(boundingRect(), m_data.toString());
animation.setEndValue(0);
animation.start();
} else {
qCritical() << "Error unknown binding type!";
return;
}
}
But like I said, the animation that I start within the painter never actually fires.
Any tips? Anyone ever done this before? I've been banging my head on this for about a week.

How about doing it in qml only this ways :
Define a custom element of your own type, that behaves the way you want it to.
Use this element instead of traditional element to be animated.
eg. I have create a custom 'AnimatedText' type to have the fading in and fading out behavior on the text elements whenever text related to them changes.
File 1 : AnimatedText.qml
import QtQuick 1.0
Item
{
id: topParent
property string aText: ""
property string aTextColor: "black"
property int aTextFontSize: 10
property int aTextAnimationTime : 1000
Behavior on opacity { NumberAnimation { duration: aTextAnimationTime } }
onATextChanged:
{
topParent.opacity = 0
junkTimer.running = true
}
Timer
{
id: junkTimer
running: false
repeat: false
interval: aTextAnimationTime
onTriggered:
{
junkText.text = aText
topParent.opacity = 1
}
}
Text
{
id: junkText
anchors.centerIn: parent
text: ""
font.pixelSize: aTextFontSize
color: aTextColor
}
}
and in your main.qml
import QtQuick 1.0
Rectangle
{
id: topParent
width: 360
height: 360
AnimatedText
{
id: someText
anchors.centerIn: parent
aText: "Click Me to change!!!.."
aTextFontSize: 25
aTextColor: "green"
aTextAnimationTime: 500
}
MouseArea
{
anchors.fill: parent
onClicked:
{
someText.aText = "Some random junk"
}
}
}

Related

Adjust translation speed of QML DragHandler

my question is about using a QML DragHandler to move a QML Item. I have successfully implemented position through dragging (when holding the Ctrl modifier) like so:
DragHandler {
dragThreshold: 0
acceptedModifiers: Qt.ControlModifier
}
Now I would like to add another handler that allows me to precisely position the element. Other software does this throught the use of the shift modifier. So what I want to do is move the element not by the pixel amount that the mouse moves, but an amount smaller than that. Ideally I would want to do something like this:
DragHandler {
dragThreshold: 0
acceptedModifiers: Qt.ShiftModifier
onActiveTranslationChanged: {
activeTranslation *= 0.5;
}
}
Unfortunatelly activeTranslation is read-only and I don't see any other property I could use and I can't think of any other way to do it... Does anybody have an idea?
Thank you very much in advance!
Unfortunately Qt doesn't provide any way to change the drag speed AFAIK.
But this is a way to achieve it:
Rectangle
{
id: theDraggableElement
width: 100
height: width
color: "red"
DragHandler
{
id: dragHandlerFast
dragThreshold: 0
acceptedModifiers: Qt.ControlModifier
target: theDraggableElement
}
}
Item
{
id: invisibleItemForSlowDragging
width: theDraggableElement.width
height: theDraggableElement.height
Binding { restoreMode: Binding.RestoreBinding; when: !dragHandlerSlow.active; target: invisibleItemForSlowDragging; property: "x"; value: theDraggableElement.x }
Binding { restoreMode: Binding.RestoreBinding; when: !dragHandlerSlow.active; target: invisibleItemForSlowDragging; property: "y"; value: theDraggableElement.y }
DragHandler
{
id: dragHandlerSlow
dragThreshold: 0
acceptedModifiers: Qt.ShiftModifier
target: invisibleItemForSlowDragging
onTranslationChanged:
{
theDraggableElement.x = invisibleItemForSlowDragging.x - dragHandlerSlow.translation.x / 2
theDraggableElement.y = invisibleItemForSlowDragging.y - dragHandlerSlow.translation.y / 2
}
}
}
I have tested this with Qt 5.15.2.

Can not send keyPressEvent to qml

I am using a QQuickWindow, QQuickRenderControl, and a QOffscreenSurface to render a Qml scene into an OpenGL framebuffer object.For this reason, I am using qt example Qt/Examples/Qt-5.15.2/quick/rendercontrol as a base with some minor modifications.
Since no window is actually created, it becomes necessary to artificially transfer mouse and keyboard events to QQuickWindow.There was already mousePressEvent example for mouseEvents on renderControl example which works fine that means I can send mouse events to qml.
Similarly, I tried to implement keyPressEvent and keyReleaseEvent by using QCoreApplication:: sendEvent (m_quickWindow, e); function call but it does not send key events to qml even if I set force to true for a rectangle. When I debug the issue, I have observed that activeFocusItem is NULL on qquickwindow.cpp ==> deliverKeyEvent function.
After that, I put a mousearea inside rectangle and call forceActiveFocus() function for rectangle and then keyPressEvent are sent to qml successfully.Also, If I put timer and call forceActiveFocus for rectangle on some interval it also works but If I try to call same forceActiveFocus() function on Component.onCompleted it is not sending any key events to qml.
So Do I have to call forceActiveFocus() to manage key sending for offscreen surface? Also if I have to call forceActiveFocus() from qml, then is there any way to call it instead of timer or mousearea?
Following is my code snippets:
import QtQuick 2.5
import QtQuick.Window 2.2
Rectangle {
visible: true
width: 1280
height: 480
color: "#000"
Rectangle
{
id:keyPressRect
width:100
height: 100
//focus: true
color: activeFocus? "red": "blue"
onFocusChanged: console.log("focus changed to: " + focus)
onActiveFocusChanged: console.log("active focus changed to: " +activeFocus)
Component.onCompleted:
{
console.log("onCompleted(), focus: " + focus + " activeFocus: " + activeFocus)
forceActiveFocus()
console.log("onCompleted(), focus: " + focus + " activeFocus: " + activeFocus)
}
/*Component.onCompleted: mTimer.start()
Timer {
id:mTimer
interval: 5000
repeat: false
onTriggered: {
keyPressRect.forceActiveFocus()
console.log("testtt")
}
}*/
Keys.onPressed:
{
console.log("key pressed on qml")
}
Keys.onReleased:
{
console.log("key released on qml")
}
MouseArea
{
anchors.fill: parent
onClicked:
{
parent.forceActiveFocus()
console.log("mouse clicked")
}
}
}
}
And sending keyPressEvent from C++ as the following:
void WindowSingleThreaded::keyPressEvent(QKeyEvent *e)
{
qDebug() << "key press event from cpp";
qDebug() << e->key() << "key is";
QCoreApplication::sendEvent(m_quickWindow, e);
}

QML can't hide Rectangle

I have a Window containing what I called a Page - a Rectangle the size of the main window:
LoginWindow {
id: loginWindow
}
SelectionWindow {
id: selectionWindow
}
ServiceWindow {
id: serviceWindow
}
ConfirmWindow {
id: confirmWindow
}
IssueWindow {
id: issueWindow
}
Each *Window here is an element inheriting from Page.
Now I can jump between different Page elements setting their visibility. Only one Page can be visible at a time. The last Page is IssueWindow and from there I want to switch back to LoginWindow and logout the user.
From C++ I emit a signal issueFinished and in MainWindow I have this code:
onIssueFinished: {
// This line won't hide the Page
issueWindow.visible = false;
loginHandle.logout();
}
As noted in the comment, the Page element just won't hide, while other elements will change their visibility. I am using GammaRay to see the changes and everything works, just hiding the issueWindow doesn't. Also when printing the issueWindow.visible to console, it's false.
Use opacity instead of visibility, and bind one method on opacity change which handle the enabled and disabled state like.
onIssueFinished: {
// This line won't hide the Page
issueWindow.opacity = 0.0;
loginHandle.logout();
}
IssueWindow {
id: issueWindow
onOpacityChanged: {
enabled = ( opacity === 1.0)
}
}
You need to implement opacity change in all the windows.
And set the opacity for all other windows.
Or you can use stack view or swipe view.
Found out the issue was actually incorrect order of switching visibility and firing signals and responding to them.
To make sure only one Page is visible, you might use an index.
You set the visibility to:
visible: (selected == thisPageIdentifier)
With this you make sure, only the page with the corresponding pageIdentifyer is visible, and you don't have to throw around multiple signals to show and hide.
When a new page shall become visible, just set the selected property to the corresponding identifier (int or string or what ever) and the other pages turn invisible.
This should increase scalability as you do not have new signals to handle in all the other pages, if you decide to add a new page once. You only need to make sure, the identifier is unique.
Your example code adjusted to this:
Item {
property int selection: 0
LoginWindow {
id: loginWindow
visible: (selection === 0)
}
SelectionWindow {
id: selectionWindow
visible: (selection === 1)
}
ServiceWindow {
id: serviceWindow
visible: (selection === 2)
}
ConfirmWindow {
id: confirmWindow
visible: (selection === 3)
}
IssueWindow {
id: issueWindow
visible: (selection === 4)
}
}

QtQuick, Dynamic Images and C++

I'm new to Qt, and from what I've read on qt-project.org and other places; QtQuick seems like an attractive option because of its ability to work on both pointer and touch based devices. My problem is getting it to work well with c++.
I decided to write a variant of Conway's Game of Life as a next step after "Hello World". I am thoroughly mystified as to how to get the "board" -- a [height][width][bytes-per-pixel] array of char -- integrated into the scene graph.
Basically, the process is that the "LifeBoard" iterates through its rules and updates the char*/image. I've got this simple QML:
:::QML
ApplicationWindow {
id: life_app_window
visible: true
title: qsTr("Life")
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Quit")
onTriggered: Qt.quit();
}
}
}
toolBar: ToolBar {
id: lifeToolBar;
ToolButton {
id: toolButtonQuit
text: qsTr("Quit")
onClicked: Qt.quit()
}
ToolButton {
id: toolButtonStop
text: qsTr("Stop")
enabled: false
//onClicked:
}
ToolButton {
id: toolButtonStart
text: qsTr("Start")
enabled: true
//onClicked: //Start life.
}
ToolButton {
id: toolButtonReset
text: qsTr("Stop")
// onClicked: //Reset life.
}
}
Flow {
id: flow1
anchors.fill: parent
//*****
// WHAT GOES HERE
//*****
}
statusBar: StatusBar {
enabled: false
Text {
// Get me from number of iterations
text: qsTr("Iterations.")
}
}
}
I want to image to come from a class with a api kinda like this:
class Life {
public:
QImage getImage() {}
// Or
char* getPixels(int h, int w, QImage::Format_ARGB8888) {}
}
I have no clue, and hours wading through tutorials did not help. How does one link a char* image in c++ to a ??? in QML so that the QML can start/stop the "Life" loop and so that the "Life" loop and update the char array and notify QML to redraw it?
Note: I've looked at subclassing QQuickImageProvider based on the info here. The problem with this approach is that I cannot see how to let c++ "drive" the on screen image. I wish to pass control from QML to c++ and let c++ tell QML when to update the display with the changed image. Is there a solution with this approach? Or another approach entirely.
First way to do that would be creating a Rectangle for each game pixel in QML, which might be fancy for a 8x8 board, but not for a 100x100 board, since you need to write the QML code manually for each pixel.
Thus I'd go for images created in C++ and exposed to QML. You call them via an image provider to allow asynchronous loading. Let Life do the logic only.
The image is called from QML like this:
Image {
id: board
source: "image://gameoflife/board"
height: 400
width: 400
}
Now gameoflife is the name of the image provider and board the so-called id you can use later.
Register gameoflife in you main.cpp
LifeImageProvider *lifeIP = new LifeImageProvider(life);
engine.addImageProvider("gameoflife", lifeIP);
where engine is your main QQmlApplicationEngine and life an instance of your Life game engine.
LifeImageProvider is your class to create pixeldata. Starts somehow like
class LifeImageProvider : public QQuickImageProvider
{
public:
LifeImageProvider(Life *myLifeEngine);
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize);
private:
Life *myLifeEngine_;
};
The important method is requestPixmap, which is called from QML. You need to implement it.
To refresh the game board when Life sends a stateChanged() signal, expose life as a global object to QML:
context->setContextProperty("life", &life);
You can bind the signal to QML
Image {
id: board
source: "image://gameoflife/board"
height: 400
width: 400
}
Connections {
target: life
onStateChanged: {
board.source = "image://gameoflife/board?" + Math.random()
// change URL to refresh image. Add random URL part to avoid caching
}
}
Just for fun, and at the risk of downvotes for a completely tangential answer, here's a GameOfLife implemented entirely in QML, just put it in a .qml file and run it with qmlscene. Works on Qt 5.3.0, and runs surprisingly (to me) fast on an old Core 2 Duo lappy. I'm sure it'll never be as fast/efficient as a C++ QQuickImageProvider based solution though, but it does make the point it's possible to do quite a lot in QML without resorting to C++.
import QtQuick 2.2
Rectangle {
id: main
width: 640
height: 640
color: '#000088'
Timer {
interval: 1000/60
running: true
repeat: true
onTriggered: {advance();display();}
}
Component {
id: cellComponent
Rectangle {
objectName: 'cell'
property int row: 0
property int col: 0
x: main.width/2+width*col
y: main.height/2+height*row
width: 5
height: 5
radius: 2
smooth: true
color: '#ffcc00'
}
}
property var cells: null
Component.onCompleted: {
cells=[[-1, 0],[-1, 1],[ 0,-1],[ 0, 0],[ 1, 0]];
display();
}
function display() {
// Just completely regenerate display field each frame
// TODO: might be nicer to do differential updates, would allow birth/death animations
// Nuke all previously displayed cells
for (var i=0;i<children.length;i++) {
if (children[i].objectName=='cell') {
children[i].destroy();
}
}
// Show current set of cells
for (var i=0;i<cells.length;i++) {
var c=cellComponent.createObject(
main,
{'row':cells[i][0],'col':cells[i][1]}
);
}
}
function advance() {
// Build a hash of the currently alive cells and a neighbour count (includes self)
var a=new Object;
var n=new Object;
for (var i=0;i<cells.length;i++) {
var p=cells[i]
var r=p[0];
var c=p[1];
if (!(r in a)) a[r]=new Object;
a[r][c]=1;
for (var dr=r-1;dr<=r+1;dr++) {
for (var dc=c-1;dc<=c+1;dc++) {
if (!(dr in n)) n[dr]=new Object;
if (!(dc in n[dr])) n[dr][dc]=0;
n[dr][dc]+=1;
}
}
}
// For all live cells, assess viability
var kill=[];
var stay=[];
for (var r in a) {
for (var c in a[r]) {
if (n[r][c]-1<2 || n[r][c]-1>3)
kill.push([Number(r),Number(c)]);
else
stay.push([Number(r),Number(c)]);
}
}
// For neighbours of live cells, assess potential for births
var born=[];
for (var r in n) {
for (var c in n[r]) {
if (!((r in a) && (c in a[r]))) {
if (n[r][c]==3)
born.push([Number(r),Number(c)]);
}
}
}
cells=stay.concat(born)
}
}
And for a pure QML version using GLSL (via a recursive QML ShaderEffect) to compute the Game of Life rules on GPU see here.

QML ListView multiselection

How can I select a few elements in the QML ListView and send its indices to C++ code?
Do something like that: if an element is clicked, set its property selected (or however you call it), and set in delegate that if selected is true, then it should be formatted differently. Plus add it to some list, to work with it.
I am pretty sure there is no way to make a QML ListView multi-selectable. Qt Declarative is focused on touch screen use and there is no meaningful way to multiselect in a pure touch UI.
i had the same issue and i found the best way to implement it, is to create a new role to the listview. Lets assume it is firstname and selected. you need to use both onCurrentIndexChanged and onClicked, because if you scroll, this will change the item but it is not a click. In both of them change the role selected into true, or adjust as it suits you, may be you don't need scroll to select and thus use only the onClicked. When clicked you can change the role selected into true
onCurrentIndexChanged:
{
mListModel.append({"firstName": newEntry,"selected":true})
}
and
onClicked:
{
mListModel.append({"firstName": newEntry,"selected":true})
}
then you may use a highlight in the deligate, this will change the color based on the state of the selected.
Here is a full code that is tested to work
//copyright: Dr. Sherif Omran
//licence: LPGL (not for commercial use)
import QtQuick 2.12
import QtQuick.Layouts 1.12
Item {
property string addnewitem:""
property int removeitemindex: -1
property string appenditemstring: ""
property int appenditemindx:-1
property int fontpoint: 20
property int radiuspoint: 14
property int spacingvalue: 0
property string delegate_color:"beige"
property string delegate_border_color:"yellowgreen"
property string highlight_color:"deeppink"
signal selectedvalueSignal (string iTemstring, string stateval)
property string sv: ""
property int indexcopy:0
id:lstmodelitem
width: parent.width
height: parent.height
ListModel {
id : mListModel
// ListElement {
// firstName : "John"
// }
}
ColumnLayout {
anchors.fill: parent
ListView{
id : mListViewId
model:mListModel
delegate :delegateId
Layout.fillWidth : true
Layout.fillHeight: true
clip: true
snapMode: ListView.SnapToItem //this stops the view at the boundary
spacing: spacingvalue
highlight: Rectangle
{
id: highlightid
width: parent.width
color: mListModel.selected==="true"?"blue":highlight_color
border.color: "beige"
z:3
opacity: 0.2
}
highlightRangeMode: ListView.StrictlyEnforceRange
highlightFollowsCurrentItem:true
onCurrentIndexChanged:
{
console.log("olistdynamic Indexchanged" + currentIndex)
mListViewId.currentIndex=currentIndex
lstmodelitem.selectedvalueSignal(currentIndex, mListModel.selected)
indexcopy=currentIndex
}
}
}
function getindex()
{
console.log("current index = " + indexcopy)
return mListViewId.currentIndex
}
function setindex(index)
{
//console.log("olistdynamic set index"+index)
mListViewId.currentIndex=index
}
function add2Item(newEntry,statev){
console.log("added item with value = " + newEntry + "state " + statev)
mListModel.append({"firstName": newEntry,"selected":statev})
}
function deleteItem(index){
mListModel.remove(index,1)
}
function appendIdem(index,valueEntry,newselectedsate)
{
console.log("append item")
mListModel.set(index,{"firstName": valueEntry,"selected":newselectedsate})
}
Component {
id : delegateId
Rectangle {
id : rectangleId
width : parent.width // Remember to specify these sizes or you'll have problems
height: textId.implicitHeight*1.2
color: selected==="true"?"blue":delegate_color
border.color: delegate_border_color
radius: radiuspoint
Text {
id : textId
anchors.centerIn: parent
text : firstName
font.pointSize: fontpoint
}
MouseArea {
anchors.fill: parent
onClicked: {
lstmodelitem.selectedvalueSignal(mListModel.firstName,mListModel.selected)
mListViewId.currentIndex=index
console.log("current index = " + index)
indexcopy=index
appendIdem(index,firstName,"true")
}
onClipChanged:
{
//console.log("a")
}
}
}
}
//if the item has been changed from null to text
onAddnewitemChanged: {
console.log("added item" + addnewitem)
add2Item(addnewitem)
}
//remove item with index
onRemoveitemindexChanged: {
console.log("remove item")
deleteItem(removeitemindex)
}
//to change the item, change the index first then the string
onAppenditemstringChanged: {
appendIdem(appenditemindx,appenditemstring)
}
}
You may try to get the ListItem's data and store it to an array on odd click and remove the ListItem's data from the array on even click. May be a simple workout, instead of creating a list of check box like items.