| 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 | |
| 4 | #include "qquicksprite_p.h" |
| 5 | #include "qquickimagebase_p.h" |
| 6 | #include <qqml.h> |
| 7 | #include <QQmlContext> |
| 8 | #include <QDebug> |
| 9 | #include <QRandomGenerator> |
| 10 | |
| 11 | QT_BEGIN_NAMESPACE |
| 12 | |
| 13 | /*! |
| 14 | \qmltype Sprite |
| 15 | \nativetype QQuickSprite |
| 16 | \inqmlmodule QtQuick |
| 17 | \ingroup qtquick-visual-utility |
| 18 | \brief Specifies sprite animations. |
| 19 | |
| 20 | Sprite defines a series of one or more frames to be animated and rendered by SpriteSequence. |
| 21 | The sprites can be in the middle of an image file, or split along multiple rows, as long as they form |
| 22 | a contiguous line wrapping to the next row of the file from the left edge of the file. |
| 23 | |
| 24 | For full details, see the \l{Sprite Animations} overview. |
| 25 | */ |
| 26 | /*! |
| 27 | \qmlproperty int QtQuick::Sprite::duration |
| 28 | |
| 29 | Duration of the animation. Values below 0 are invalid. |
| 30 | |
| 31 | If frameRate is valid then it will be used to calculate the duration of the frames. |
| 32 | If not, and frameDuration is valid, then frameDuration will be used. Otherwise duration is used. |
| 33 | */ |
| 34 | /*! |
| 35 | \qmlproperty int QtQuick::Sprite::durationVariation |
| 36 | |
| 37 | The duration of the animation can vary by up to this amount. Variation will never decrease the |
| 38 | length of the animation to less than 0. |
| 39 | |
| 40 | durationVariation will only take effect if duration is |
| 41 | used to calculate the duration of frames. |
| 42 | |
| 43 | Default is 0. |
| 44 | */ |
| 45 | |
| 46 | /*! |
| 47 | \qmlproperty real QtQuick::Sprite::frameRate |
| 48 | |
| 49 | Frames per second to show in the animation. Values below 0 are invalid. |
| 50 | |
| 51 | If frameRate is valid then it will be used to calculate the duration of the frames. |
| 52 | If not, and frameDuration is valid , then frameDuration will be used. Otherwise duration is used. |
| 53 | */ |
| 54 | /*! |
| 55 | \qmlproperty real QtQuick::Sprite::frameRateVariation |
| 56 | |
| 57 | The frame rate between animations can vary by up to this amount. Variation will never decrease the |
| 58 | length of the animation to less than 0. |
| 59 | |
| 60 | frameRateVariation will only take effect if frameRate is |
| 61 | used to calculate the duration of frames. |
| 62 | |
| 63 | Default is 0. |
| 64 | */ |
| 65 | |
| 66 | /*! |
| 67 | \qmlproperty int QtQuick::Sprite::frameDuration |
| 68 | |
| 69 | Duration of each frame of the animation in milliseconds. Values below 0 are invalid. |
| 70 | |
| 71 | If frameRate is valid then it will be used to calculate the duration of the frames. |
| 72 | If not, and frameDuration is valid, then frameDuration will be used. Otherwise duration is used. |
| 73 | */ |
| 74 | /*! |
| 75 | \qmlproperty int QtQuick::Sprite::frameDurationVariation |
| 76 | |
| 77 | The duration of a frame in the animation can vary by up to this amount. Variation will never decrease the |
| 78 | length of the animation to less than 0. |
| 79 | |
| 80 | frameDurationVariation will only take effect if frameDuration is |
| 81 | used to calculate the duration of frames. |
| 82 | |
| 83 | Default is 0. |
| 84 | */ |
| 85 | |
| 86 | /*! |
| 87 | \qmlproperty string QtQuick::Sprite::name |
| 88 | |
| 89 | The name of this sprite, for use in the to property of other sprites. |
| 90 | */ |
| 91 | /*! |
| 92 | \qmlproperty QVariantMap QtQuick::Sprite::to |
| 93 | |
| 94 | A list of other sprites and weighted transitions to them, |
| 95 | for example {"a":1, "b":2, "c":0} would specify that one-third should |
| 96 | transition to sprite "a" when this sprite is done, and two-thirds should |
| 97 | transition to sprite "b" when this sprite is done. As the transitions are |
| 98 | chosen randomly, these proportions will not be exact. With "c":0 in the list, |
| 99 | no sprites will randomly transition to "c", but it wll be a valid path if a sprite |
| 100 | goal is set. |
| 101 | |
| 102 | If no list is specified, or the sum of weights in the list is zero, then the sprite |
| 103 | will repeat itself after completing. |
| 104 | */ |
| 105 | /*! |
| 106 | \qmlproperty int QtQuick::Sprite::frameCount |
| 107 | |
| 108 | Number of frames in this sprite. |
| 109 | */ |
| 110 | /*! |
| 111 | \qmlproperty int QtQuick::Sprite::frameHeight |
| 112 | |
| 113 | Height of a single frame in this sprite. |
| 114 | */ |
| 115 | /*! |
| 116 | \qmlproperty int QtQuick::Sprite::frameWidth |
| 117 | |
| 118 | Width of a single frame in this sprite. |
| 119 | */ |
| 120 | /*! |
| 121 | \qmlproperty int QtQuick::Sprite::frameX |
| 122 | |
| 123 | The X coordinate in the image file of the first frame of the sprite. |
| 124 | */ |
| 125 | /*! |
| 126 | \qmlproperty int QtQuick::Sprite::frameY |
| 127 | |
| 128 | The Y coordinate in the image file of the first frame of the sprite. |
| 129 | */ |
| 130 | /*! |
| 131 | \qmlproperty url QtQuick::Sprite::source |
| 132 | |
| 133 | The image source for the animation. |
| 134 | |
| 135 | If frameHeight and frameWidth are not specified, it is assumed to be a single long row of square frames. |
| 136 | Otherwise, it can be multiple contiguous rows or rectangluar frames, when one row runs out the next will be used. |
| 137 | |
| 138 | If frameX and frameY are specified, the row of frames will be taken with that x/y coordinate as the upper left corner. |
| 139 | */ |
| 140 | |
| 141 | /*! |
| 142 | \qmlproperty bool QtQuick::Sprite::reverse |
| 143 | |
| 144 | If true, then the animation will be played in reverse. |
| 145 | |
| 146 | Default is false. |
| 147 | */ |
| 148 | |
| 149 | /*! |
| 150 | \qmlproperty bool QtQuick::Sprite::randomStart |
| 151 | |
| 152 | If true, then the animation will start its first animation with a random amount of its duration skipped. |
| 153 | This allows them to not look like they all just started when the animation begins. |
| 154 | |
| 155 | This only affects the very first animation played. Transitioning to another animation, or the same |
| 156 | animation again, will not trigger this. |
| 157 | |
| 158 | Default is false. |
| 159 | */ |
| 160 | |
| 161 | /*! |
| 162 | \qmlproperty bool QtQuick::Sprite::frameSync |
| 163 | |
| 164 | If true, then the animation will have no duration. Instead, the animation will advance |
| 165 | one frame each time a frame is rendered to the screen. This synchronizes it with the painting |
| 166 | rate as opposed to elapsed time. |
| 167 | |
| 168 | If frameSync is set to true, it overrides all of duration, frameRate and frameDuration. |
| 169 | |
| 170 | Default is false. |
| 171 | */ |
| 172 | |
| 173 | static const int unsetDuration = -2;//-1 means perframe for duration |
| 174 | QQuickSprite::QQuickSprite(QObject *parent) |
| 175 | : QQuickStochasticState(parent) |
| 176 | , m_generatedCount(0) |
| 177 | , m_framesPerRow(0) |
| 178 | , m_rowY(0) |
| 179 | , m_rowStartX(0) |
| 180 | , m_reverse(false) |
| 181 | , m_frameHeight(0) |
| 182 | , m_frameWidth(0) |
| 183 | , m_frames(1) |
| 184 | , m_frameX(0) |
| 185 | , m_frameY(0) |
| 186 | , m_frameRate(unsetDuration) |
| 187 | , m_frameRateVariation(0) |
| 188 | , m_frameDuration(unsetDuration) |
| 189 | , m_frameDurationVariation(0) |
| 190 | , m_frameSync(false) |
| 191 | , m_devicePixelRatio(1.0) |
| 192 | { |
| 193 | } |
| 194 | |
| 195 | /*! \internal */ |
| 196 | QQuickSprite::~QQuickSprite() |
| 197 | { |
| 198 | } |
| 199 | |
| 200 | int QQuickSprite::variedDuration() const //Deals with precedence when multiple durations are set |
| 201 | { |
| 202 | if (m_frameSync) |
| 203 | return 0; |
| 204 | |
| 205 | if (m_frameRate != unsetDuration) { |
| 206 | qreal fpms = (m_frameRate |
| 207 | + (m_frameRateVariation * QRandomGenerator::global()->generateDouble() * 2) |
| 208 | - m_frameRateVariation) / 1000.0; |
| 209 | return qMax(a: qreal(0.0) , b: m_frames / fpms); |
| 210 | } else if (m_frameDuration != unsetDuration) { |
| 211 | int mspf = m_frameDuration |
| 212 | + (m_frameDurationVariation * QRandomGenerator::global()->generateDouble() * 2) |
| 213 | - m_frameDurationVariation; |
| 214 | return qMax(a: 0, b: m_frames * mspf); |
| 215 | } else if (duration() >= 0) { |
| 216 | qWarning() << "Sprite::duration is changing meaning to the full animation duration." ; |
| 217 | qWarning() << "Use Sprite::frameDuration for the old meaning, of per frame duration." ; |
| 218 | qWarning() << "As an interim measure, duration/durationVariation means the same as frameDuration/frameDurationVariation, and you'll get this warning spewed out everywhere to motivate you." ; |
| 219 | //Note that the spammyness is due to this being the best location to detect, but also called once each animation loop |
| 220 | return QQuickStochasticState::variedDuration() * m_frames; |
| 221 | } |
| 222 | return 1000; //When nothing set |
| 223 | } |
| 224 | |
| 225 | void QQuickSprite::startImageLoading() |
| 226 | { |
| 227 | m_pix.clear(this); |
| 228 | if (!m_source.isEmpty()) { |
| 229 | const QQmlContext *context = qmlContext(this); |
| 230 | QQmlEngine *e = context ? context->engine() : nullptr; |
| 231 | if (!e) { //If not created in QML, you must set the QObject parent to the QML element so this can work |
| 232 | context = qmlContext(parent()); |
| 233 | e = context ? context->engine() : nullptr; |
| 234 | if (!e) |
| 235 | qWarning() << "QQuickSprite: Cannot find QQmlEngine - this class is only for use in QML and may not work" ; |
| 236 | } |
| 237 | const QUrl resolvedUrl = context ? context->resolvedUrl(m_source) : m_source; |
| 238 | QUrl loadUrl = resolvedUrl; |
| 239 | QQuickImageBase::resolve2xLocalFile(url: resolvedUrl, targetDevicePixelRatio: m_devicePixelRatio, sourceUrl: &loadUrl, |
| 240 | sourceDevicePixelRatio: &m_devicePixelRatio); |
| 241 | |
| 242 | m_pix.load(e, loadUrl); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | QT_END_NAMESPACE |
| 247 | |
| 248 | #include "moc_qquicksprite_p.cpp" |
| 249 | |