Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquickanimatedsprite.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include "qquicksprite_p.h"
8#include <QtQuick/private/qsgcontext_p.h>
9#include <QtQuick/private/qquickitem_p.h>
10#include <private/qsgadaptationlayer_p.h>
11#include <private/qqmlglobal_p.h>
12#include <QtQuick/qsgnode.h>
13#include <QtQuick/qsgtexturematerial.h>
14#include <QtQuick/qsgtexture.h>
15#include <QtQuick/qquickwindow.h>
16#include <QtQml/qqmlinfo.h>
17#include <QFile>
18#include <cmath>
19#include <qmath.h>
20#include <QDebug>
21
23
260{
262 d->m_sprite = new QQuickSprite(this);
263
265 connect(this, SIGNAL(widthChanged()),
266 this, SLOT(reset()));
268 this, SLOT(reset()));
269}
270
272{
273 Q_D(const QQuickAnimatedSprite);
274 return d->m_running;
275}
276
278{
279 Q_D(const QQuickAnimatedSprite);
280 return d->m_interpolate;
281}
282
284{
285 Q_D(const QQuickAnimatedSprite);
286 return d->m_sprite->source();
287}
288
290{
291 Q_D(const QQuickAnimatedSprite);
292 return d->m_sprite->reverse();
293}
294
296{
297 Q_D(const QQuickAnimatedSprite);
298 return d->m_sprite->frameSync();
299}
300
302{
303 Q_D(const QQuickAnimatedSprite);
304 return d->m_sprite->frames();
305}
306
308{
309 Q_D(const QQuickAnimatedSprite);
310 return d->m_sprite->frameHeight();
311}
312
314{
315 Q_D(const QQuickAnimatedSprite);
316 return d->m_sprite->frameWidth();
317}
318
320{
321 Q_D(const QQuickAnimatedSprite);
322 return d->m_sprite->frameX();
323}
324
326{
327 Q_D(const QQuickAnimatedSprite);
328 return d->m_sprite->frameY();
329}
330
332{
333 Q_D(const QQuickAnimatedSprite);
334 return d->m_sprite->frameRate();
335}
336
338{
339 Q_D(const QQuickAnimatedSprite);
340 return d->m_sprite->frameDuration();
341}
342
344{
345 Q_D(const QQuickAnimatedSprite);
346 return d->m_loops;
347}
348
350{
351 Q_D(const QQuickAnimatedSprite);
352 return d->m_paused;
353}
354
356{
357 Q_D(const QQuickAnimatedSprite);
358 return d->m_curFrame;
359}
360
362{
363 Q_D(const QQuickAnimatedSprite);
364 return d->m_finishBehavior;
365}
366
367bool QQuickAnimatedSprite::isCurrentFrameChangedConnected()
368{
370}
371
372void QQuickAnimatedSprite::reloadImage()
373{
374 if (!isComponentComplete())
375 return;
376 createEngine();//### It's not as inefficient as it sounds, but it still sucks having to recreate the engine
377}
378
380{
382 createEngine();
384 if (d->m_running) {
385 d->m_running = false;
386 start();
387 }
388}
389
400{
402 if (d->m_running)
403 return;
404 d->m_running = true;
405 if (!isComponentComplete())
406 return;
407 d->m_curLoop = 0;
408 d->m_curFrame = 0;
409 d->m_timestamp.start();
410 if (d->m_spriteEngine) {
411 d->m_spriteEngine->stop(0);
412 d->m_spriteEngine->updateSprites(0);
413 d->m_spriteEngine->start(0);
414 }
416 emit runningChanged(true);
417 maybeUpdate();
418}
419
430{
432 if (!d->m_running)
433 return;
434 d->m_running = false;
435 if (!isComponentComplete())
436 return;
437 d->m_pauseOffset = 0;
438 emit runningChanged(false);
439 maybeUpdate();
440}
441
448{
450 if (!frames)
451 return;
452 //TODO-C: May not work when running - only when paused
453 d->m_curFrame += frames;
454 while (d->m_curFrame < 0)
455 d->m_curFrame += d->m_spriteEngine->maxFrames();
456 d->m_curFrame = d->m_curFrame % d->m_spriteEngine->maxFrames();
457 emit currentFrameChanged(d->m_curFrame);
458 maybeUpdate();
459}
460
461void QQuickAnimatedSprite::maybeUpdate()
462{
464 const auto &extraData = priv->extra;
465 if ((extraData.isAllocated() && extraData->effectRefCount > 0) || priv->effectiveVisible)
466 update();
467}
468
470{
472 if (change == ItemVisibleHasChanged && d->m_running && !d->m_paused)
473 maybeUpdate();
475}
476
486{
488
489 if (d->m_paused)
490 return;
491 d->m_pauseOffset = d->m_timestamp.elapsed();
492 d->m_paused = true;
493 emit pausedChanged(true);
494 maybeUpdate();
495}
496
506{
508
509 if (!d->m_paused)
510 return;
511 d->m_pauseOffset = d->m_pauseOffset - d->m_timestamp.elapsed();
512 d->m_paused = false;
513 emit pausedChanged(false);
514 maybeUpdate();
515}
516
518{
520
521 if (d->m_running != arg) {
522 if (d->m_running)
523 stop();
524 else
525 start();
526 }
527}
528
530{
531 Q_D(const QQuickAnimatedSprite);
532
533 if (d->m_paused != arg) {
534 if (d->m_paused)
535 resume();
536 else
537 pause();
538 }
539}
540
542{
544
545 if (d->m_interpolate != arg) {
546 d->m_interpolate = arg;
548 }
549}
550
552{
554
555 if (d->m_sprite->m_source != arg) {
556 const qreal targetDevicePixelRatio = (window() ? window()->effectiveDevicePixelRatio() : qApp->devicePixelRatio());
557 d->m_sprite->setDevicePixelRatio(targetDevicePixelRatio);
558 d->m_sprite->setSource(arg);
560 reloadImage();
561 }
562}
563
565{
567
568 if (d->m_sprite->m_reverse != arg) {
569 d->m_sprite->setReverse(arg);
571 }
572}
573
575{
577
578 if (d->m_sprite->m_frameSync != arg) {
579 d->m_sprite->setFrameSync(arg);
581 if (d->m_running)
582 restart();
583 }
584}
585
587{
589
590 if (d->m_sprite->m_frames != arg) {
591 d->m_sprite->setFrameCount(arg);
593 reloadImage();
594 }
595}
596
598{
600
601 if (d->m_sprite->m_frameHeight != arg) {
602 d->m_sprite->setFrameHeight(arg);
605 reloadImage();
606 }
607}
608
610{
612
613 if (d->m_sprite->m_frameWidth != arg) {
614 d->m_sprite->setFrameWidth(arg);
617 reloadImage();
618 }
619}
620
622{
624
625 if (d->m_sprite->m_frameX != arg) {
626 d->m_sprite->setFrameX(arg);
628 reloadImage();
629 }
630}
631
633{
635
636 if (d->m_sprite->m_frameY != arg) {
637 d->m_sprite->setFrameY(arg);
639 reloadImage();
640 }
641}
642
644{
646
647 if (d->m_sprite->m_frameRate != arg) {
648 d->m_sprite->setFrameRate(arg);
650 if (d->m_running)
651 restart();
652 }
653}
654
656{
658
659 if (d->m_sprite->m_frameDuration != arg) {
660 d->m_sprite->setFrameDuration(arg);
662 if (d->m_running)
663 restart();
664 }
665}
666
671
676
678{
680
681 if (d->m_loops != arg) {
682 d->m_loops = arg;
684 }
685}
686
687void QQuickAnimatedSprite::setCurrentFrame(int arg) //TODO-C: Probably only works when paused
688{
690
691 if (d->m_curFrame != arg) {
692 d->m_curFrame = arg;
693 Q_EMIT currentFrameChanged(arg); //TODO-C Only emitted on manual advance!
694 update();
695 }
696}
697
699{
701
702 if (d->m_finishBehavior != arg) {
703 d->m_finishBehavior = arg;
704 Q_EMIT finishBehaviorChanged(arg);
705 }
706}
707
708void QQuickAnimatedSprite::createEngine()
709{
711
712 if (d->m_spriteEngine)
713 delete d->m_spriteEngine;
714 QList<QQuickSprite*> spriteList;
715 spriteList << d->m_sprite;
716 d->m_spriteEngine = new QQuickSpriteEngine(QList<QQuickSprite*>(spriteList), this);
717 d->m_spriteEngine->startAssemblingImage();
718 reset();
719}
720
721QSGSpriteNode* QQuickAnimatedSprite::initNode()
722{
724
725 if (!d->m_spriteEngine) {
726 qmlWarning(this) << "No sprite engine...";
727 return nullptr;
728 } else if (d->m_spriteEngine->status() == QQuickPixmap::Null) {
729 d->m_spriteEngine->startAssemblingImage();
730 maybeUpdate();//Schedule another update, where we will check again
731 return nullptr;
732 } else if (d->m_spriteEngine->status() == QQuickPixmap::Loading) {
733 maybeUpdate();//Schedule another update, where we will check again
734 return nullptr;
735 }
736
737 QImage image = d->m_spriteEngine->assembledImage(d->sceneGraphRenderContext()->maxTextureSize()); //Engine prints errors if there are any
738 if (image.isNull())
739 return nullptr;
740
741 // If frameWidth or frameHeight are not explicitly set, frameWidth
742 // will be set to the width of the image divided by the number of frames,
743 // and frameHeight will be set to the height of the image.
744 // In this case, QQuickAnimatedSprite currently won't emit frameWidth/HeightChanged
745 // at all, so we have to do this here, as it's the only place where assembledImage()
746 // is called (which calculates the "implicit" frameWidth/Height.
747 // In addition, currently the "implicit" frameWidth/Height are only calculated once,
748 // even after changing to a different source.
751
752 QSGSpriteNode *node = d->sceneGraphContext()->createSpriteNode();
753
754 d->m_sheetSize = QSize(image.size() / image.devicePixelRatio());
755 node->setTexture(window()->createTextureFromImage(image));
756 d->m_spriteEngine->start(0);
757 node->setTime(0.0f);
758 node->setSourceA(QPoint(d->m_spriteEngine->spriteX(), d->m_spriteEngine->spriteY()));
759 node->setSourceB(QPoint(d->m_spriteEngine->spriteX(), d->m_spriteEngine->spriteY()));
760 node->setSpriteSize(QSize(d->m_spriteEngine->spriteWidth(), d->m_spriteEngine->spriteHeight()));
761 node->setSheetSize(d->m_sheetSize);
762 node->setSize(QSizeF(width(), height()));
763 return node;
764}
765
767{
769 d->m_pleaseReset = true;
770 maybeUpdate();
771}
772
774{
776
777 if (d->m_pleaseReset) {
778 delete oldNode;
779
780 oldNode = nullptr;
781 d->m_pleaseReset = false;
782 }
783
784 QSGSpriteNode *node = static_cast<QSGSpriteNode *>(oldNode);
785 if (!node)
786 node = initNode();
787
788 if (node)
789 prepareNextFrame(node);
790
791 if (d->m_running && !d->m_paused)
792 maybeUpdate();
793
794 return node;
795}
796
797void QQuickAnimatedSprite::prepareNextFrame(QSGSpriteNode *node)
798{
800
801 int timeInt = d->m_timestamp.elapsed() + d->m_pauseOffset;
802 qreal time = timeInt / 1000.;
803
804 int frameAt;
805 qreal progress = 0.0;
806 int lastFrame = d->m_curFrame;
807 if (d->m_running && !d->m_paused) {
808 const int nColumns = d->m_sheetSize.width() / d->m_spriteEngine->spriteWidth();
809 //Advance State (keeps time for psuedostates)
810 d->m_spriteEngine->updateSprites(timeInt);
811
812 //Advance AnimatedSprite
813 qreal animT = d->m_spriteEngine->spriteStart()/1000.0;
814 const int frameCountInRow = d->m_spriteEngine->spriteFrames();
815 const qreal frameDuration = d->m_spriteEngine->spriteDuration() / frameCountInRow;
816 if (frameDuration > 0) {
817 qreal frame = (time - animT)/(frameDuration / 1000.0);
818 bool lastLoop = d->m_loops > 0 && d->m_curLoop == d->m_loops-1;
819 //don't visually interpolate for the last frame of the last loop
820 const int max = lastLoop ? frameCountInRow - 1 : frameCountInRow;
821 frame = qBound(qreal(0.0), frame, qreal(max));
822 double intpart;
823 progress = std::modf(frame,&intpart);
824 frameAt = (int)intpart;
825 const int rowIndex = d->m_spriteEngine->spriteY()/frameHeight();
826 const int newFrame = rowIndex * nColumns + frameAt;
827 if (d->m_curFrame > newFrame) //went around
828 d->m_curLoop++;
829 d->m_curFrame = newFrame;
830 } else {
831 d->m_curFrame++;
832 if (d->m_curFrame >= d->m_spriteEngine->maxFrames()) { // maxFrames: total number of frames including all rows
833 d->m_curFrame = 0;
834 d->m_curLoop++;
835 }
836 frameAt = d->m_curFrame % nColumns;
837 if (frameAt == 0)
838 d->m_spriteEngine->advance();
839 progress = 0;
840 }
841 if (d->m_loops > 0 && d->m_curLoop >= d->m_loops) {
842 if (d->m_finishBehavior == FinishAtInitialFrame)
843 frameAt = 0;
844 else
845 frameAt = frameCount() - 1;
846 d->m_curFrame = frameAt;
847 d->m_running = false;
848 emit runningChanged(false);
849 emit finished();
850 maybeUpdate();
851 }
852 } else {
853 frameAt = d->m_curFrame;
854 }
855 if (d->m_curFrame != lastFrame) {
856 if (isCurrentFrameChangedConnected())
857 emit currentFrameChanged(d->m_curFrame);
858 maybeUpdate();
859 }
860
861 int frameCount = d->m_spriteEngine->spriteFrames();
862 bool reverse = d->m_spriteEngine->sprite()->reverse();
863 if (reverse)
864 frameAt = (frameCount - 1) - frameAt;
865
866 int w = d->m_spriteEngine->spriteWidth();
867 int h = d->m_spriteEngine->spriteHeight();
868 int x1;
869 int y1;
870 if (d->m_paused) {
871 int spriteY = d->m_spriteEngine->spriteY();
872 if (reverse) {
873 int rows = d->m_spriteEngine->maxFrames() * d->m_spriteEngine->spriteWidth() / d->m_sheetSize.width();
874 spriteY -= rows * d->m_spriteEngine->spriteHeight();
875 frameAt = (frameCount - 1) - frameAt;
876 }
877
878 int position = frameAt * d->m_spriteEngine->spriteWidth() + d->m_spriteEngine->spriteX();
879 int row = position / d->m_sheetSize.width();
880
881 x1 = (position - (row * d->m_sheetSize.width()));
882 y1 = (row * d->m_spriteEngine->spriteHeight() + spriteY);
883 } else {
884 x1 = d->m_spriteEngine->spriteX() + frameAt * w;
885 y1 = d->m_spriteEngine->spriteY();
886 }
887
888 //### hard-coded 0/1 work because we are the only
889 // images in the sprite sheet (without this we cannot assume
890 // where in the sheet we begin/end).
891 int x2;
892 int y2;
893 if (reverse) {
894 if (frameAt > 0) {
895 x2 = x1 - w;
896 y2 = y1;
897 } else {
898 x2 = d->m_sheetSize.width() - w;
899 y2 = y1 - h;
900 if (y2 < 0) {
901 //the last row may not fill the entire width
902 int maxRowFrames = d->m_sheetSize.width() / d->m_spriteEngine->spriteWidth();
903 if (d->m_spriteEngine->maxFrames() % maxRowFrames)
904 x2 = ((d->m_spriteEngine->maxFrames() % maxRowFrames) - 1) * w;
905
906 y2 = d->m_sheetSize.height() - h;
907 }
908 }
909 } else {
910 if (frameAt < (frameCount-1)) {
911 x2 = x1 + w;
912 y2 = y1;
913 } else {
914 x2 = 0;
915 y2 = y1 + h;
916 if (y2 >= d->m_sheetSize.height())
917 y2 = 0;
918 }
919 }
920
921 node->setSourceA(QPoint(x1, y1));
922 node->setSourceB(QPoint(x2, y2));
923 node->setSpriteSize(QSize(w, h));
924 node->setTime(d->m_interpolate ? progress : 0.0);
925 node->setSize(QSizeF(width(), height()));
926 node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
927 node->update();
928}
929
931
932#include "moc_qquickanimatedsprite_p.cpp"
\inmodule QtGui
Definition qimage.h:37
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
\inmodule QtCore\reentrant
Definition qpoint.h:25
void start()
\qmlmethod QtQuick::AnimatedSprite::start()
void interpolateChanged(bool arg)
QSGNode * updatePaintNode(QSGNode *, UpdatePaintNodeData *) override
Called on the render thread when it is time to sync the state of the item with the scene graph.
void frameWidthChanged(int arg)
void componentComplete() override
Invoked after the root component that caused this instantiation has completed construction.
void loopsChanged(int arg)
void advance(int frames=1)
\qmlmethod int QtQuick::AnimatedSprite::advance()
void sourceChanged(const QUrl &arg)
void resume()
\qmlmethod int QtQuick::AnimatedSprite::resume()
void pausedChanged(bool arg)
void frameSyncChanged(bool arg)
void setSource(const QUrl &arg)
QQuickAnimatedSprite(QQuickItem *parent=nullptr)
\qmltype AnimatedSprite \instantiates QQuickAnimatedSprite \inqmlmodule QtQuick \inherits Item
void pause()
\qmlmethod int QtQuick::AnimatedSprite::pause()
void frameXChanged(int arg)
void frameDurationChanged(int arg)
void setFinishBehavior(FinishBehavior arg)
void reverseChanged(bool arg)
void itemChange(ItemChange, const ItemChangeData &) override
Called when change occurs for this item.
void frameHeightChanged(int arg)
void runningChanged(bool arg)
void stop()
\qmlmethod QtQuick::AnimatedSprite::stop()
void frameRateChanged(qreal arg)
void frameCountChanged(int arg)
void currentFrameChanged(int arg)
void frameYChanged(int arg)
static QQuickItemPrivate * get(QQuickItem *item)
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:63
void heightChanged()
void setFlag(Flag flag, bool enabled=true)
Enables the specified flag for this item if enabled is true; if enabled is false, the flag is disable...
void widthChanged()
void componentComplete() override
\reimp Derived classes should call the base class method before adding their own actions to perform a...
QQuickWindow * window() const
Returns the window in which this item is rendered.
qreal width
This property holds the width of this item.
Definition qquickitem.h:75
virtual void itemChange(ItemChange, const ItemChangeData &)
Called when change occurs for this item.
bool isComponentComplete() const
Returns true if construction of the QML component is complete; otherwise returns false.
void setImplicitHeight(qreal)
QPointF position() const
bool smooth
\qmlproperty bool QtQuick::Item::smooth
Definition qquickitem.h:112
qreal height
This property holds the height of this item.
Definition qquickitem.h:76
ItemChange
Used in conjunction with QQuickItem::itemChange() to notify the item about certain types of changes.
Definition qquickitem.h:144
@ ItemVisibleHasChanged
Definition qquickitem.h:148
void setImplicitWidth(qreal)
void update()
Schedules a call to updatePaintNode() for this item.
\group qtquick-scenegraph-nodes \title Qt Quick Scene Graph Node classes
Definition qsgnode.h:37
\inmodule QtQuick
Definition qsgtexture.h:20
\inmodule QtCore
Definition qsize.h:208
\inmodule QtCore
Definition qsize.h:25
\inmodule QtCore
Definition qurl.h:94
Combined button and popup list for selecting options.
Definition image.cpp:4
#define qApp
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
static const QMetaObjectPrivate * priv(const uint *data)
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
#define SLOT(a)
Definition qobjectdefs.h:52
#define SIGNAL(a)
Definition qobjectdefs.h:53
GLfloat GLfloat GLfloat w
[0]
GLuint GLfloat GLfloat GLfloat GLfloat y1
GLuint GLfloat GLfloat GLfloat x1
GLfloat GLfloat GLfloat GLfloat h
GLfixed GLfixed GLfixed y2
GLfixed GLfixed x2
GLenum GLenum GLsizei void * row
#define IS_SIGNAL_CONNECTED(Sender, SenderType, Name, Arguments)
Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me)
SSL_CTX int void * arg
#define Q_EMIT
#define emit
double qreal
Definition qtypes.h:187
QFrame frame
[0]
\inmodule QtQuick
Definition qquickitem.h:159