I program a "game" where a ball is sent to bounce against the walls.
Before hurt the walls, the behavior on y is a spring animation. When it hurt the wall, the ball should bounce and have a linear movement.
My problem is I can't stop the initial Behavior.
I have tried several way :
disabled the behavior but the movement in progress continue
stop the SpringAnimation : I have the error : " QML SpringAnimation: setRunning() cannot be used on non-root animation nodes."
This is the code
Rectangle {
id: ball
property bool enableMoving: false
function newMousePos(mX,mY){
if(enableMoving){
ball.x = mX - ball.width/2
ball.y = mY - ball.height/2
}
}
Behavior on x {enabled: true;id: behavX; SpringAnimation {id:spX; spring: 0.8; damping: 0.9; mass: 10; } }
Behavior on y {enabled: true;id: behavY; SpringAnimation {id:spY; spring: 0.8; damping: 0.9; mass: 10 ; } }
onXChanged: {
if(ball.x>main.width-ball.size) {
console.log("wall 2 ")
spX.stop(); spY.stop()
}
if(ball.x<0) {
...
}
}
onYChanged: {
...
}
Timer{
id:timerDisableMoving
interval: 500; running:true
onTriggered: enableMoving=false
}
}
I don't understand why I can't stop the behavior. Is it possible to define behavior on state to solve the problem ?
Put this code on signal when ball hits wall:
behavX.enabled = false
This will disable behavior (I have tested it in my project).
If you only want to disable specific animation then do it like this:
spX.running = false //ignore this, it is equivalent to spX.stop()
I just thought that you might putted code like this:
function disableBehavior()
{
behavX.enabled = false
//...
behavX.enabled = true
}
If this is the case, then it wont work because during the time of execution animation wont happen.
Instead, you should do asynchronous call, for example like this:
function disableBehavior()
{
behavX.enabled = false
//...
myTimer.start()
}
//...
Timer
{
id: myTimer
interval: 1
running: false
onTriggered:
{
behavX.enabled = true
stop()
}
}
But, I think that you will want to call "behavior enabler" after your "linear behavior" is finished (instead of creating new timer).
I think, I had similar problem. I had some Behavior, which animated change of some property. And I wanted to be able to stop it in the middle, while it did not yet complete.
It did not work. By the way, QML also was complaining in the console, that I try to use some method on non-root Animation.
The short answer:
It seems, that it is not supported by the Behavior to be stopped in the middle.
Though, it can be interrupted by setting new value to the property. This would restart the animation to target the new value. When I realized it, I implemented the "stop" function like this:
function stopAnimation() {
// disable animation for the next value change
moveBehavior.enabled = false
// Change value of the property of interest.
// This stops currently running animation
y += 1
// Restore initial value of the property
y -= 1
// Enable animation for subsequent property changes
moveBehavior.enabled = true
}
So, I disable the behavior to make the change of properties instant. Then I change target property to something very close to the current value (I believe, I cannot use the same value, because then "onChange" signal will not be emitted). At this point running animation should be stopped. Then I reset property to its original value. And then enable animation again to handle further property changes.
Looks not that good, but it worked for me with Behavior...
After thinking a bit more I come to the conclusion, that I probably should not use Behavior. Then I just created PropertyAnimation{} tied to my property of interest directly in the object, which needs to be moved. And then I just dynamically assign target value and duration and with now "start()" and "stop()" just work as expected.
Full solution looks like this (what it does, you can probably figure out):
Item {
id: root
focus: true
Rectangle {
id: racket
height: 40
width: 10
x: 20
y: 20
color: "blue"
property int maxY: 460
// ...
PropertyAnimation {
id: moveDownAnimation
target: racket
property: "y"
to: racket.maxY
// if you want constant velocity, you need
// to calculate duration dynamically based
// on desired velocity and distance
duration: 1000
}
function moveUp () { // just return racket to the initial position
y = 50
}
function moveDown() {
moveDownAnimation.start()
}
function stopAnimation() {
moveDownAnimation.stop()
}
}
Keys.onPressed: {
if (event.isAutoRepeat) {
return
}
if (event.key === Qt.Key_Q) {
racket.moveUp() // return to initial position
} else if (event.key === Qt.Key_A) {
racket.moveDown()
}
}
Keys.onReleased: {
if (event.isAutoRepeat) {
return
}
if (event.key === Qt.Key_A) {
racket.stopAnimation()
}
}
}
Related
How to exit from an application by clicking the back button twice on your mobile?
for going back to the previous page i have done this
Keys.onBackPressed: {
stackView.push("previous.qml")
}
Keys.onBackPressed: {
timer.pressBack()
}
Timer{
id: timer
property bool backPressed: false
repeat: false
interval: 200//ms
onTriggered: backPressed = false
function pressBack(){
if(backPressed){
timer.stop()
backPressed = false
// leave application
// your code
leaveApp()
}
else{
backPressed = true
timer.start()
}
}
}
You can use a timer to do this, within 50ms if you press twice on back this will run leaveApp().
I am developing a project by seperating ui.qml files and .qml files . So, I am writing the functionality codes(javascript codes) into .qml file and I am writing design codes in ui.qml file . However I have a problem with using Component.onComplete functionality in .qml file.
for example :
MapDisplayForm.ui.qml
Item{
id:item1
property alias map1
Map{
id: map1
Component.OnCompleted : {
//this is the function that i should write in Map.qml
}
}
}
MapDisplay.qml
MapDisplayForm{
//it does not accept map1.oncompleted here
}
You can solve this problem using the QML Connections item.
As an example in my form1.qml I have
SlideToAction {
id: sosSlider
height: 80
Component.onCompleted: {
if (window.getHomeLocation()) {
normalBMargin = 0
} else {
normalBMargin = -80
}
}
}
To put this into the form1Form.ui.qml and form1.qml format:
form1Form.ui.qml:
property alias sosSlider:sosSlider
SlideToAction {
id: sosSlider
height: 80
}
The property alias sosSlider:sosSlider is like saying this property is public and won't work otherwise.
form1.qml:
Connections {
target: sosSlider
Component.onCompleted {
// Your code here
}
}
This solved the problem for me.
I like the idea of splitting up the UI and functionality. It reminds of the MVC style that Ionic/Angular and also Django views use. It makes it really easy to play around with the UI without worrying about dependencies or other qml restrictions.
Hope this helps!
You can do something like the following:
Item {
id:item1
signal mapCompleted() // <-- custom signal
property alias mapDisplay
Map {
id: map1
Component.OnCompleted : {
item1.mapCompleted(); // <-- emit custom signal
}
}
}
and so:
MapForm {
onMapCompleted: { // <-- handle custom signal
}
}
UI files are meant to be used ONLY with the designer (which does not work pretty well with imperative JS). The solutions can be:
Remove the .ui or use common .qml files
Use an property to handle the onCompleted event and use that property in your qml file to do what you want like the next example.
MapDisplayForm.ui.qml
Item {
id:item1
property bool propertyOnCompleted: false
Map {
id: map1
Component.onCompleted: propertyOnCompleted = true
}
MapDisplay.qml
MapDisplayForm {
onPropertyOnCompletedChanged: {
console.log("Map1 Completed")
}
}
I want to automatically show/hide view depending on a property's collection size, here's the code:
QtObject {
property var controlWindow: Window {
property var collection: []
signal sigAddElement(var element)
onSigAddElement: {
collection.push(element)
}
signal sigEraseAllElements()
onSigEraseAllElements: {
collection.length = 0
}
onCollectionChanged: {
console.log("collection.len = " + collection.length)
}
Rectangle {
id: autoHidableView
visible: collection.length != 0
}
}
}
but visible property of autoHidableView evaluates only once on startup and never evaluates again
The onCollectionChanged handler is never get called which is understandable since collection object itself stays the same
So is it possible to listen for collection's size change event?
The problem is that the javascript array that you create with property var collection: [] does not have any signals (the onCollectionChanged is indeed when you would assign a new collection to it). You better use ListModel:
QtObject {
property var controlWindow: Window {
ListModel {
id: collection
}
signal sigAddElement(var element)
onSigAddElement: {
collection.append(element)
}
signal sigEraseAllElements()
onSigEraseAllElements: {
collection.clear()
}
Rectangle {
id: autoHidableView
visible: collection.count > 0
}
}
}
Note that you need to change the push to append
Listening to the array length is not enough.
See var QML Basic Type:
It is important to note that changes in regular properties of JavaScript objects assigned to a var property will not trigger updates of bindings that access them.
It is the same behavior when assigning an array to the var. The binding will only be reevaluated if the property is reassigned with an entire new object/array.
You have 2 ways to make your binding listen to the collection length change:
Reassign the entire array:
onSigAddElement: {
collection = collection.concat(element)
}
// ...
onSigEraseAllElements: {
collection = []
}
Trigger the change signal manually:
onSigAddElement: {
collection.push(element)
collectionChanged()
}
// ...
onSigEraseAllElements: {
collection.length = 0
collectionChanged()
}
I have a C++ class and I made it possible to be able to create it in QML. Then I have a signal in QML which has an argument representing this object. I am using the QtQml.StateMachine and I am catching triggered signals with SignalTransition. I want to be able to set my signals argument to the next state when the SignalTransition triggers. In code:
This is how my signal looks like in Model.qml:
signal mySignal(CustomObject customObject)
My signal transitioning code in State.qml:
import QtQml.StateMachine 1.0 as SM
// SM.State { ...
Model {
id: model
// ...
}
SM.SignalTransition {
targetState: nextState
signal: model.mySignal
onTriggered: console.log(customObject) // error here
}
// ... }
I get the following error: ReferenceError: customObject is not defined.
When I am emitting the signal I pass my customObject as an argument for the signal.
This is a bit of a hack, but the guard is passed the signal's parameters. The guard is an expression which is evaluated to see if it's true (and if so, the transition applies) but there's nothing stopping you running extra code in there. So:
State {
targetState: nextState
signal: model.mySignal
guard: {
// here, customObject is available, because it was an arg to the signal
// so, stash it away somewhere where you can get at it later
root.currentCustomObject = customObject;
// and explicitly return true, so that this guard passes OK and
// then the transition actually happens
return true;
}
}
One approach would be to have the mySignal handler set a property that can be summarily accessed by the less-flexible SignalTransition, like so:
Model {
id: model
property CustomObject currentObj
onMySignal: currentObj = customObject
}
SM.SignalTransition {
targetState: nextState
signal: model.currentObjChanged
onTriggered: console.log(model.currentObj)
}
Hacky and not tested, but might be acceptable for this case.
In QML it is impossible to call .disconnect() without arguments for a signal:
file:mainwindow.qml:107: Error: Function.prototype.disconnect: no arguments given
So how can I disconnect ALL slots without specifying each of them?
Or maybe it is possible by passing signal object to C++ and disconnect it somehow there?
Or maybe any workaround exists?
The goal I want to reach is to change behavior of an object by connecting different slots to it's signal. For example:
object.disconnect() // disconnect all slots
object.connect(one_super_slot)
object.disconnect() // disconnect all slots
object.connect(another_super_slot)
No. I looked at the source code in qv4objectwrapper.cpp, and you can see this code:
void QObjectWrapper::initializeBindings(ExecutionEngine *engine)
{
engine->functionClass->prototype->defineDefaultProperty(QStringLiteral("connect"), method_connect);
engine->functionClass->prototype->defineDefaultProperty(QStringLiteral("disconnect"), method_disconnect);
}
Those are the only two methods that are added. If you look at the source code for method_disconnect() you can see that it always requires one or two parameters, including the name of the slot to disconnect.
There is no disconnectAll() unfortunately.
Okay, 5 minutes after my question I've made a workaround: connect only once to one signal that calls jsobject from inside:
Item {
property var fire
// Any qml object. In this example it is ActionExecutor which emits actionRequest
ActionExecutor {
//signal actionRequest(int actionType)
onActionRequest: fire(actionType)
}
Action {
shortcut: "Ctrl+S"
text: "One action"
onTriggered: {
parent.fire = function(actionType) {
console.log('one slot')
}
}
}
Action {
shortcut: "Ctrl+X"
text: "Another action"
onTriggered: {
parent.fire = function(actionType) {
console.log('Another slot')
}
}
}
}
So that js object can be reassigned many times as you want so you may change your behavior by reassigning this object. If you want to disconnect all simple assign undefined to fire. Also you can make a chain of "slots" by modifying code to something like:
Item {
property var fire
property var slots: [
function(actionType) {
console.log('1: ' + actionType)
},
function() {
console.log('2: ' + actionType)
},
function() {
console.log('3: ' + actionType)
}
]
// Any qml object. In this example it is ActionExecutor which emits actionRequest
ActionExecutor {
//signal actionRequest(int actionType)
onActionRequest: fire(actionType)
}
Action {
shortcut: "Ctrl+S"
text: "One action"
onTriggered: {
parent.fire = function(actionType) {
console.log('calling all custom JS-slots')
for (var i in slots) {
slots[i](actionType)
}
}
}
}
}
So anyone can implement own signal-slot architecture in qml as a simple javascript observer pattern.
Enjoy.