I am building a C++ application based on QML.
To make it simple :
In my main QML file, I have a button (Rectangle) calling a JavaScript function (defined in an external JS file) when clicked:
// My JS file linked to the main QML window
[...]
function actionOnButtonClicked()
{
var x = 0;
var y = 0;
for(var i = 0; i < 3; i++)
{
createObject(x, y);
x = x + 10;
y = y + 10;
}
}
As you can see, in this function, I call n (= 3 here) times another JS function to dynamically create several QML objects to add to the scene:
function createObject(xPosition, yPosition)
{
component = Qt.createComponent("Symbol.qml");
component.createObject(windowApp, {"x": xPosition, "y": yPosition});
}
This is working fine. But the created object (Symbol) appears in the windowApp with a translation animation (around 1sec.), and I would like to wait for the first object's animation to complete before creating the second one...
As we cannot use setTimeOut() JavaScript function in QML, I wonder how I could achieve this. I do not see how I could make use of the QML Timer object or even PauseAnimation...
Does somebody know how to add a delay between 2 QML JavaScript operations ?
I think this QML Timer type can help you achieve what you want.
import QtQuick 2.0
Item {
Timer {
interval: 500; running: true; repeat: true
onTriggered: time.text = Date().toString()
}
Text { id: time }
}
You could probably do it so that you only create one "Symbol" from your button action and trigger a new symbol on some event in the new object. Perhaps the animation ending triggers an event that you could use ?
Its been a while, I have missed QML. But let me try to suggest a solution. I guess this might work, if you are calling that translationAnimation.running = true in Component.onComlpeted event. I have posted a stupid answer before. Now I replace it with a lazy/ugly way to do this. This is probably not the right way to do it, though this code may help your use case.
CreateObject.js
.pragma library
var objects = null;
var objectCount = 0;
var i = 0;
var mainWin;
var x = 0;
var y = 0;
function calledOnbuttonAction(parentWindow)
{
if(objects === null)
{
mainWin = parentWindow;
x = 0;
y = 0;
objects = new Array();
createObject(x,y);
}
else
{
if(x <= mainWin.width)
x = x + 28;
else
{
x = 0;
if(y <= mainWin.height)
y = y + 28;
else
{
console.debug("Exceeded window area!")
return;
}
}
createObject(x,y);
}
}
function createObject(xPos, yPos)
{
i++;
var component = Qt.createComponent("Object.qml");
objects[objectCount++] = component.createObject(mainWin, {"x": xPos, "y": yPos});
}
main.qml
import QtQuick 1.1
import "CreateObjects.js" as CreateObject
Rectangle {
id: mainWindow
width: 360
height: 360
Text {
text: qsTr("Click inside window")
anchors.centerIn: parent
font.pixelSize: 18
}
MouseArea {
anchors.fill: parent
onClicked: {
CreateObject.calledOnbuttonAction(mainWindow); //passing the parent window object
}
}
}
Object.qml //Symbol in your case
//The Symbol
import QtQuick 1.1
import "CreateObjects.js" as CreateObject
Rectangle {
id: obj
width: 25
height: 25
gradient: Gradient {
GradientStop {
position: 0
color: "#d11b1b"
}
GradientStop {
position: 1
color: "#ea4848"
}
}
property alias animationStatus: completedAnimation
NumberAnimation {
id: completedAnimation;
target: obj;
property: "opacity";
duration: 800;
from: 0;
to: 1.0;
onRunningChanged: {
if(!running && CreateObject.i < 900) // Decrease or increase the value according to the number of objects you want to create
{
CreateObject.calledOnbuttonAction();
}
}
}
Component.onCompleted: completedAnimation.running = true;
}
Related
I am working on an application where I'd like to provide overlays of different animations onto a range of videos using p5js. I'm looking to organize my classes of animation types so that each animation has a similar structure to update and destroy objects during each loop. My plan is to have an array of animations that are currently "active" update them each iteration of the loop and then destroy them when they are completed. I built a class to fade text in this manner but I'm getting some weird flashy behavior that seems to occur every time a new animation is triggered in the middle of another animation. I've been trying to debug it but have been unsuccessful. Do you have any suggestions as to:
(1) if this is due to my code structure? (and maybe you have a suggestion of a better way),
or
(2) I'm doing something else incorrectly?
Here is the code:
// create an array of currently executing animations to update
// each animation class needs to have one function and one attribute:
// (1) update() -- function to move the objects where ever they need to be moved
// (2) done -- attribute to determine if they should be spliced out of the array
var animations = [];
//////////////////////////////////////////
// Global Variables for Animations //
//////////////////////////////////////////
let start = false;
let count = 0;
function setup(){
let canv = createCanvas(1920, 1080);
canv.id = "myP5canvas";
background(0);
}
function draw(){
background(0);
// Check things to see if we should be adding any animations to the picture
var drawText = random(100);
if (drawText > 98) {
//if (start == false) {
let r = 255;
let g = 204;
let b = 0;
let x = random(width-10);
let y = random(height-10);
animations.push(new TextFader("Wowwwzers!", 100, 'Georgia', r, g, b, x, y, count));
start = true;
count += 1;
}
// Update animations that exist!
for (var i=0; i < animations.length; i++) {
// update the position/attributes of the animation
animations[i].update();
// check if the animation is done and should be removed from the array
if (animations[i].done) {
console.log("SPLICE: " + animations[i].id);
animations.splice(i, 1);
}
}
}
// EXAMPLE ANIMATION
// TEXT FADE
let TextFader = function(words, size, font, red, green, blue, xloc, yloc, id) {
this.id = id;
console.log("create fader: " + this.id);
// translating inputs to variables
this.text = words;
this.size = size;
this.font = font;
// To Do: separating out each of the values until I figure out how to fade separately from the color constructor
this.red = red;
this.green = green;
this.blue = blue;
this.xloc = xloc;
this.yloc = yloc;
// Maybe add customization in the future for fading...
this.fade = 255;
this.fadeTime = 3; // in seconds
this.fadeIncrement = 5;
// Variables to use for destruction
this.createTime = millis();
this.done = false;
}
TextFader.prototype.update = function() {
// Update the fade
// If the fade is below zero don't update and set to be destroyed
this.fade -= this.fadeIncrement;
if (this.fade <= 0) {
this.done = true;
} else {
this.show();
}
}
TextFader.prototype.show = function() {
textFont(this.font);
textSize(this.size);
fill(this.red, this.green, this.blue, this.fade);
text(this.text, this.xloc, this.yloc);
console.log("Drawing: " + this.id + " fade: " + this.fade + " done: " + this.done);
}
Yay, I've got you an answer! It works like expected when you reverse the for loop that loops over the animations.
Because you splice elements of the same array inside the loop, some elements are skipped. For example; animations[0].done = true and gets removed. That means that animations[1] is now in the spot of animations[0] and animations[2] is now in the spot of animations[1].
The i variable is incremented to 1, so on the next loop, you update animations[1] (and skip the animation that is now in animation[0]).
When you reverse the loop, everything before the element you splice stays the same and nothing is skipped.
For example; animations[2].done = true and gets removed. That means that animations[1] is still in the spot of animations[1].
The i variable is decremented to 1, so on the next loop, you update animations[1] and don't skip any elements.
// Update animations that exist!
for (var i = animations.length - 1; i >= 0; i--) {
// update the position/attributes of the animation
animations[i].update();
// check if the animation is done and should be removed from the array
if (animations[i].done) {
//console.log("SPLICE: " + animations[i].id);
animations.splice(i, 1);
}
}
I have the following class:
// Population class
class Population {
constructor(size, target, mutationRate, minFitness) {
this.target = target;
this.size = size;
this.individuals = [];
// contains phrases (strings)
this.matePool = [];
this.mutationRate = mutationRate;
this.bestFitness = 0;
this.bestPhrase = "";
this.totalGenerations = 0;
// Stopping criterion
this.minFitness = minFitness;
}
clearMatePool () {
this.matePool = [];
}
addIndividual(newIndividual) {
this.individuals.push(newIndividual);
}
evaluate() {
let fitness = 0;
for (let i = 0; i < this.size; i++) {
fitness = this.individuals[i].fitnessFunct(this.target);
// Update best fitness and best phrase
if (fitness > this.bestFitness) {
this.bestFitness = fitness;
this.bestPhrase = this.individuals[i].getGenotype();
}
}
// Stopping criterion
if (this.bestFitness < this.minFitness) {
return true;
}
else {
return false;
}
}
buildMatePool() {
for (let i = 0; i < this.size; i++) {
let n = Math.round(this.individuals[i].getFitness() * 100);
for (let j = 0; j < n; j++) {
this.matePool.push(this.individuals[i].phrase);
}
}
}
reproduce() {
// Create new generation
for (let i = 0; i < this.size; i++) {
// Pick 2 parents
let a, b, child, midpoint;
while (true) {
// Index of parentA
a = getRandomIntInclusive(0, this.matePool.length - 1);
// Index of parentB
b = getRandomIntInclusive(0, this.matePool.length - 1);
// Be sure you have picked two unique parents (phrases)
if (this.matePool[a] === this.matePool[b]) {
continue;
}
else {
break;
}
}
// Crossover
child = this.crossover(a, b);
// Mutation
this.mutation(child);
// The new child is part of the new population
this.individuals[i] = child;
}
this.totalGenerations += 1;
}
crossover(a, b) {
let child = new Individual(this.target.length);
child.setGenotype("");
let midpoint = getRandomIntInclusive(0, this.target.length-1);
for (let i = 0; i < this.target.length; i++) {
if (i < midpoint) {
child.phrase = child.phrase + this.matePool[a].charAt(i);
}
else {
child.phrase = child.phrase + this.matePool[b].charAt(i);
}
}
return child;
}
mutation(individual) {
for (let i = 0; i < this.target.length; i++) {
// The block inside the conditional statement would be executed 1% of the time.
if(Math.random() < this.mutationRate) {
// replace char with a new random character
individual.phrase = individual.phrase.substr(0, i) + String.fromCharCode(getRandomIntInclusive(32, 128)) + individual.phrase.substr(i + 1);
}
}
}
}
I have the following DOM elements:
// Shows the current generation
var totalGenerationsHTML = $('#total-generations');
// Shows the best phrase so far
var bestPhraseHTML = $('#best-phrase');
// Shows the best fitness so far
var bestFitnessHTML = $('#best-fitness');
// Shows the result of the reproduction process (child)
var processingHTML = $('#processing');
And the following code section:
var condition = population.evaluate();
while (condition) {
// Processing
population.buildMatePool();
population.reproduce();
population.clearMatePool();
condition = population.evaluate();
}
I need to update the value of the DOM elements in each iteration. I've tried different loop implementations using setInterval() and setTimeout() but the output was out of sync. Any help will be useful.
P.S. I am new to JavaScript. Please excuse any mistakes.
while (condition) {}
Is so called blocking code, since that loop runs without giving the browser a chance to actually render the result, you wont see anything and probably the UI freezes.
You need to use either setTimeout, setInterval or as recommended for animation requestAnimationFrame
but the output was out of sync
Changing the DOM and so visible things on a page is an asynchronous Process and while Js is running, the browser does not repaint. A while loop is running JS.
For more detailed information have a look here
#philipp was right. requestAnimationFrame() is the solution.
Basically requestAnimationFrame() is:
Synchronized with browser repaint (setTimeout and setInterval are not)
Optimized for animation
Runs as fast as the screen will allow
Battery saver
The following HTML/JS/CSS snippet demonstrates the use of requestAnimationFrame().
var currentPos = -200;
var element = document.getElementById("heading");
// Animation loop
function moveRight() {
currentPos += 5;
heading.style.left = currentPos + "px";
window.requestAnimationFrame(moveRight);
if(currentPos >= 800) {
currentPos = -200;
}
}
moveRight();
h1 {
position: relative;
border: 5px solid black;
text-align: center;
width: 200px;
}
div {
width: 800px;
height: 400px;
background-color: lime;
margin: 0 auto;
overflow: hidden;
}
<!DOCTYPE html>
<html>
<head>
<title>Animation Loops</title>
</head>
<body>
<div>
<p>requestAnimationFrame()</p>
<ol>
<li>Synchronized with browser repaint</li>
<li>Optimized for animation</li>
<li>Runs as fast as the screen will allow</li>
<li>Battery saver</li>
</ol>
<p>
Traditionally to create an animation in JavaScript, we relied
on setTimeout() called recursively or setInterval() to
repeatedly execute some code to make changes to an element
frame by frame, such as once every 50 milliseconds. What we
specify as the delay (ie: 50 milliseconds) inside these
functions are often times not honoured due to changes in user
system resources at the time, leading to inconsistent delay
intervals between animation frames.
</p>
<p>
<strong>DO NOT</strong> use setInterval() or setTimeOut() for
animations, use requestAnimationFrame() instead.
</p>
<h1 id="heading">Animation Loops</h1>
</div>
</body>
</html>
I have a C# script like below:
public List<MazePath> BreakIntoConnectedPaths()
{
List<MazeVertex> remainVertices = new List<MazeVertex>(vertices);
List<MazePath> paths = new List<MazePath>();
while (remainVertices.Count > 0)
{
MazePath path = new MazePath();
path.entrancePosition = entrancePosition;
path.exitPosition = exitPosition;
VisitCell(path, remainVertices.First(), null, remainVertices);
paths.Add(path);
//Store the coordinate for entrance and exit
}
return paths;
}
void VisitCell(MazePath path, MazeVertex ver, MazeVertex parent, List<MazeVertex> remainVertices)
{
remainVertices.Remove(ver);
path.Add(ver);
for (int i = 0; i < ver.connectVertices.Count; i++)
{
MazeVertex ver2 = ver.connectVertices[i];
if (ver2 != parent)
{
VisitCell(path, ver2, ver, remainVertices);
}
}
}
I want to convert it to javascript as below
BreakIntoConnectedPaths = function() {
var remainVertices = _.cloneDeep(this.vertices);
var paths = [];
while (remainVertices.length > 0) {
var path = new Path();
path.entrancePos = this.entrancePos;
path.exitPos = this.exitPos;
this.VisitCell(path, remainVertices[0], null, remainVertices);
paths.push(path);
// Store the coordinate for entrance and exit
}
return paths;
}
VisitCell = function(path, vertex, parentVertex, remainVertices) {
_.remove(remainVertices, function(v) {
return v.x === vertex.x && v.z === vertex.z;
});
path.Add(vertex);
for (var i = 0; i < vertex.connectVertices.length; i++) {
var connectedVertex = vertex.connectVertices[i];
// if (parentVertex && (connectedVertex.x !== parentVertex.x || connectedVertex.z !== parentVertex.z)) {
if(parentVertex && _.isEqual(connectedVertex, parentVertex)) {
VisitCell(path, connectedVertex, vertex, remainVertices);
}
}
}
The _ symbol here is lodash sign.
After I convert to javascript code, the behavior of these functions is difference with the C# one. With the same vertices data, the paths array had returned with difference size.
Thanks you for reading and pls help me if you see my mistake here.
In the C# version, your VisitCell function has a condition that says if(ver2 != parent), but in the JS version you check that they are equal instead of not equal.
Also, that condition would never pass any way because in your first call to that function you pass in null for the parent, but in that condition you check that the parent is "truthy".
Lodash's isEqual can handle null values, so I'm not sure why you're checking if the parent is truthy there. Perhaps you meant to do this?
if(!_.isEqual(connectedVertex, parentVertex)) {
There are several ways to improve your JavaScript code. When transpiling code, it is better to not copy/paste and fix, but to rewrite using the target language instead.
I would prefer to have this written, for example:
var vertices;
var entrancePos;
var exitPos;
function Path(entrancePos, exitPos){
this.entrancePos = entrancePos;
this.exitPos = exitPos;
this.Add = function() {
// your Add() code here
}
}
function breakIntoConnectedPaths() {
var remainingVertices = _.cloneDeep(vertices);
var paths = [];
while (remainVertices.length) {
var path = new Path(entrancePos, exitPos);
visitCell(path, remainingVertices.shift());
// Store the coordinate for entrance and exit
paths.push(path);
}
return paths;
}
function visitCell(path, vertex, parentVertex) {
path.Add(vertex);
for (var i = 0; i < vertex.connectVertices.length; i++) {
var connectedVertex = vertex.connectVertices[i];
if(_.isEqual(connectedVertex, parentVertex)) {
visitCell(path, connectedVertex, vertex);
}
}
}
Keep in mind that the variables vertices, entrancePos, exitPos and Path are not available to me on your C# code, so I only declare them on JavaScript. Implement them as you may.
Does that fix it, by the way?
So I created an implicit binary tree, and a simple way of navigating through that tree that reacts when, for example, you push the left arrow, right arrow, or up arrow:
var items = [ .... ];
function newItem() {
var me = this;
this.item_location = 1;
this.leftItem = function() {
var left = items[(me.item_location * 2)];
if(left){
me.theItem = //create element, insert item from 'left' var, display item
me.item_location *= 2;
} else {}
}
this.rightItem = function() {
var right = items[(me.item_location * 2) + 1];
if(right){
me.theItem = //create element, insert item from 'right' var, display item
me.item_location = (me.item_location * 2) + 1;
} else {}
this.upItem = function() {
var up = items[Math.floor(me.item_location / 2)];
if(up){
me.theItem = //as above
me.item_location = Math.floor(me.item_location / 2);
} else {
// abandon setting this item, return to a different context/menu
}
}
My issue is that I'm finding 'duplicates' that aren't actually there, so there must be a bug in either this tree navigation code or in the code I'm using to check for duplicates:
function findItem(theItem) {
var itemLocations = [];
for(var i = items.length - 1; i >= 0; i--) {
if(items[i] === theItem) {
itemLocations.push(i);
}
}
for(var i = itemLocations.length - 1; i >= 0; i--) {
console.log(itemLocations[i]);
}
console.log(itemLocations);
return itemLocations;
}
What is wrong with this? It's consistently ending up with certain entries showing up twice, but the findItem function consistently finds only one entry.
There's nothing more to the code that could possibly cause this, so I feel like I've missed something super obvious.
I want to use setInterval to animate a couple things. First I'd like to be able to specify a series of page elements, and have them set their background color, which will gradually fade out. Once the color returns to normal the timer is no longer necessary.
So I've got
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(itvlH);
// but itvlH isn't in scope...?
}
},50);
}
Further complicating the situation is I'd want to be able to have multiple instances of this going on. I'm thinking maybe I'll push the live interval handlers into an array and clean them up as they "go dead" but how will I know when they do? Only inside the interval closure do I actually know when it has finished.
What would help is if there was a way to get the handle to the interval from within the closure.
Or I could do something like this?
function intRun() {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// now I can access an array containing all handles to intervals
// but how do I know which one is ME?
clearInterval(itvlH);
}
}
var handlers = [];
function setFadeColor(nodes) {
var x = 256;
handlers.push(setInterval(intRun,50);
}
Your first example will work fine and dandy ^_^
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
clearInterval(itvlH);
// itvlH IS in scope!
}
},50);
}
Did you test it at all?
I've used code like your first block, and it works fine. Also this jsFiddle works as well.
I think you could use a little trick to store the handler. Make an object first. Then set the handler as a property, and later access the object's property. Like so:
function setFadeColor(nodes) {
var x = 256;
var obj = {};
// store the handler as a property of the object which will be captured in the closure scope
obj.itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(obj.itvlH);
// but itvlH isn't in scope...?
}
},50);
}
You can write helper function like so:
function createDisposableTimerInterval(closure, delay) {
var cancelToken = {};
var handler = setInterval(function() {
if (cancelToken.cancelled) {
clearInterval(handler);
} else {
closure(cancelToken);
}
}, delay);
return handler;
}
// Example:
var i = 0;
createDisposableTimerInterval(function(token) {
if (i < 10) {
console.log(i++);
} else {
// Don't need that timer anymore
token.cancelled = true;
}
}, 2000);