├── README.md ├── .tag ├── .gitreview ├── src ├── timeline │ ├── doc │ │ ├── images │ │ │ ├── timeline-editor.png │ │ │ └── timeline-settings.png │ │ ├── src │ │ │ ├── qtquicktimeline-toc.qdoc │ │ │ ├── qt6-changes.qdoc │ │ │ ├── qtquicktimeline-module-qml.qdoc │ │ │ ├── qtquicktimeline-index.qdoc │ │ │ └── qtquicktimeline-overview.qdoc │ │ ├── qtquicktimeline.qdocconf │ │ └── style │ │ │ └── style.css │ ├── qtquicktimelineglobal.h │ ├── blendtrees │ │ ├── qtquicktimelineblendtreesglobal.h │ │ ├── doc │ │ │ └── src │ │ │ │ └── qtquicktimelineblendtrees-module-qml.qdoc │ │ ├── qtquicktimelineblendtreesglobal_p.h │ │ ├── CMakeLists.txt │ │ ├── qblendtreenode_p.h │ │ ├── qblendanimationnode_p.h │ │ ├── qtimelineanimationnode_p.h │ │ ├── qblendtreenode.cpp │ │ ├── qtimelineanimationnode.cpp │ │ └── qblendanimationnode.cpp │ ├── qtquicktimelineglobal_p.h │ ├── CMakeLists.txt │ ├── qquicktimelineanimation_p.h │ ├── qquicktimeline_p.h │ ├── qquicktimelineanimation.cpp │ ├── qquickkeyframe_p.h │ ├── qquickkeyframedatautils_p.h │ ├── qquicktimeline.cpp │ └── qquickkeyframe.cpp └── CMakeLists.txt ├── tests ├── manual │ ├── timelineTestApp │ │ ├── timelineTestApp.pro │ │ ├── animate_bool.cbor │ │ ├── animate_color.cbor │ │ ├── animate_real.cbor │ │ ├── animate_vector3d.cbor │ │ ├── Circle.qml │ │ ├── main.cpp │ │ ├── qml.qrc │ │ ├── test03.qml │ │ ├── test07.qml │ │ ├── test01.qml │ │ ├── main.qml │ │ ├── test09.qml │ │ ├── test10.qml │ │ ├── test08.qml │ │ ├── test04.qml │ │ ├── test06.qml │ │ ├── test02.qml │ │ └── test05.qml │ └── keyframeBinaryGenerator │ │ ├── keyframeBinaryGenerator.pro │ │ ├── keyframes_file_specification.txt │ │ └── main.cpp ├── auto │ ├── CMakeLists.txt │ ├── qtquicktimeline_blendtrees │ │ ├── CMakeLists.txt │ │ ├── data │ │ │ └── BlendTreeTest.qml │ │ └── tst_blendtrees.cpp │ └── qtquicktimeline │ │ ├── CMakeLists.txt │ │ ├── data │ │ ├── deltafunction.qml │ │ ├── restorebindingtest.qml │ │ ├── vectors.qml │ │ ├── parameterization.qml │ │ └── simpletest.qml │ │ └── tst_qtquicktimeline.cpp └── CMakeLists.txt ├── dependencies.yaml ├── .cmake.conf ├── dist ├── REUSE.toml ├── changes-1.0.0 ├── changes-6.0.0 ├── changes-5.14.0 ├── changes-5.15.0 ├── changes-5.14.1 ├── changes-5.15.1 ├── changes-5.14.2 └── changes-5.15.2 ├── LICENSES ├── LicenseRef-Qt-Commercial.txt └── BSD-3-Clause.txt ├── coin ├── module_config.yaml └── axivion │ └── ci_config_linux.json ├── CMakeLists.txt ├── REUSE.toml └── licenseRule.json /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.tag: -------------------------------------------------------------------------------- 1 | d38946f5930d355aacd602355c8250ee07015f42 2 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=codereview.qt-project.org 3 | project=qt/qtquicktimeline 4 | defaultbranch=dev 5 | -------------------------------------------------------------------------------- /src/timeline/doc/images/timeline-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qt/qtquicktimeline/dev/src/timeline/doc/images/timeline-editor.png -------------------------------------------------------------------------------- /src/timeline/doc/images/timeline-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qt/qtquicktimeline/dev/src/timeline/doc/images/timeline-settings.png -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/timelineTestApp.pro: -------------------------------------------------------------------------------- 1 | QT += quick 2 | CONFIG += c++11 3 | 4 | SOURCES += main.cpp 5 | 6 | RESOURCES += qml.qrc 7 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/animate_bool.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qt/qtquicktimeline/dev/tests/manual/timelineTestApp/animate_bool.cbor -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/animate_color.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qt/qtquicktimeline/dev/tests/manual/timelineTestApp/animate_color.cbor -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/animate_real.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qt/qtquicktimeline/dev/tests/manual/timelineTestApp/animate_real.cbor -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/animate_vector3d.cbor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qt/qtquicktimeline/dev/tests/manual/timelineTestApp/animate_vector3d.cbor -------------------------------------------------------------------------------- /dependencies.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | ../qtbase: 3 | ref: df1292e2b96aab02ad6df778d8336e7958ad5d1c 4 | required: true 5 | ../qtdeclarative: 6 | ref: 7ef1d06ce70fa360613dca0b5ff03365ebbc9883 7 | required: true 8 | -------------------------------------------------------------------------------- /tests/auto/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | # Generated from auto.pro. 5 | 6 | add_subdirectory(qtquicktimeline) 7 | add_subdirectory(qtquicktimeline_blendtrees) 8 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | # Generated from src.pro. 5 | 6 | set(QT_SBOM_DEFAULT_QT_LICENSE_ID_LIBRARIES "QT_COMMERCIAL_OR_GPL3") 7 | 8 | add_subdirectory(timeline) 9 | -------------------------------------------------------------------------------- /.cmake.conf: -------------------------------------------------------------------------------- 1 | set(QT_REPO_MODULE_VERSION "6.12.0") 2 | set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") 3 | set(QT_EXTRA_INTERNAL_TARGET_DEFINES 4 | "QT_NO_CONTEXTLESS_CONNECT=1" 5 | "QT_NO_FOREACH=1" 6 | "QT_NO_QASCONST=1" 7 | "QT_NO_URL_CAST_FROM_STRING=1" 8 | ) 9 | -------------------------------------------------------------------------------- /dist/REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[annotations]] 4 | path = ["*"] 5 | precedence = "override" 6 | comment = "Licensed as documentation." 7 | SPDX-FileCopyrightText = "Copyright (C) The Qt Company Ltd." 8 | SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only" 9 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | # Generated from tests.pro. 5 | 6 | if(QT_BUILD_STANDALONE_TESTS) 7 | # Add qt_find_package calls for extra dependencies that need to be found when building 8 | # the standalone tests here. 9 | endif() 10 | qt_build_tests() 11 | -------------------------------------------------------------------------------- /src/timeline/qtquicktimelineglobal.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QTQUICKTIMELINEGLOBAL_H 5 | #define QTQUICKTIMELINEGLOBAL_H 6 | 7 | #include 8 | #include 9 | 10 | #endif // QTQUICKTIMELINEGLOBAL_H 11 | -------------------------------------------------------------------------------- /dist/changes-1.0.0: -------------------------------------------------------------------------------- 1 | Qt Quick Timeline 1.0 2 | 3 | Qt Quick Timeline 1.0 is supported from Qt 5.12 onwards. 4 | 5 | General Improvements 6 | -------------------- 7 | Introducing new component for Qt 8 | 9 | API changes (source break) 10 | -------------------------- 11 | 12 | 13 | New features 14 | ------------ 15 | - Introducing new component for Qt 16 | 17 | Fixed issues 18 | ------------ 19 | -------------------------------------------------------------------------------- /src/timeline/blendtrees/qtquicktimelineblendtreesglobal.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QTQUICKTIMELINEBLENDTREESGLOBAL_H 5 | #define QTQUICKTIMELINEBLENDTREESGLOBAL_H 6 | 7 | #include 8 | #include 9 | 10 | #endif // QTQUICKTIMELINEBLENDTREESGLOBAL_H 11 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/Circle.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | 6 | Rectangle { 7 | id: rootItem 8 | 9 | property color borderColor 10 | property real borderWidth 11 | 12 | width: 100 13 | height: 100 14 | radius: width / 2 15 | border.color: rootItem.borderColor 16 | border.width: rootItem.borderWidth 17 | } 18 | -------------------------------------------------------------------------------- /LICENSES/LicenseRef-Qt-Commercial.txt: -------------------------------------------------------------------------------- 1 | Licensees holding valid commercial Qt licenses may use this software in 2 | accordance with the the terms contained in a written agreement between 3 | you and The Qt Company. Alternatively, the terms and conditions that were 4 | accepted by the licensee when buying and/or downloading the 5 | software do apply. 6 | 7 | For the latest licensing terms and conditions, see https://www.qt.io/terms-conditions. 8 | For further information use the contact form at https://www.qt.io/contact-us. 9 | -------------------------------------------------------------------------------- /coin/module_config.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | accept_configuration: 3 | condition: property 4 | property: features 5 | not_contains_value: Disable 6 | 7 | instructions: 8 | Build: 9 | - type: EnvironmentVariable 10 | variableName: VERIFY_SOURCE_SBOM 11 | variableValue: "ON" 12 | - !include "{{qt/qtbase}}/coin_module_build_template_v2.yaml" 13 | 14 | Test: 15 | - !include "{{qt/qtbase}}/coin_module_test_template_v3.yaml" 16 | - !include "{{qt/qtbase}}/coin_module_test_docs.yaml" 17 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include 5 | #include 6 | 7 | int main(int argc, char *argv[]) 8 | { 9 | QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 10 | QGuiApplication app(argc, argv); 11 | 12 | QQmlApplicationEngine engine; 13 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 14 | if (engine.rootObjects().isEmpty()) 15 | return -1; 16 | 17 | return app.exec(); 18 | } 19 | -------------------------------------------------------------------------------- /src/timeline/doc/src/qtquicktimeline-toc.qdoc: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only 3 | 4 | /*! 5 | \page qtquicktimeline-toc.html 6 | \title Qt Quick Timeline module topics 7 | 8 | The following list has links to all the individual topics (HTML files) 9 | in the Qt Quick Timeline module. 10 | 11 | \list 12 | \li \l {Qt Quick Timeline Overview}{Overview} 13 | \li \l {Changes to Qt Quick Timeline}{Upgrading from Qt 5} 14 | \li \l {Qt Quick Timeline Blend Trees QML Types}{Reference - Blend Trees QML Types} 15 | \endlist 16 | 17 | */ 18 | -------------------------------------------------------------------------------- /src/timeline/qtquicktimelineglobal_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QTQUICKTIMELINEGLOBAL_P_H 5 | #define QTQUICKTIMELINEGLOBAL_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include "qtquicktimelineglobal.h" 19 | #include 20 | 21 | #endif // QTQUICKTIMELINEGLOBAL_P_H 22 | -------------------------------------------------------------------------------- /tests/auto/qtquicktimeline_blendtrees/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | # Collect test data 5 | file(GLOB_RECURSE test_data 6 | RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} 7 | data/* 8 | ) 9 | 10 | qt_internal_add_test(tst_blendtrees 11 | SOURCES 12 | tst_blendtrees.cpp 13 | DEFINES 14 | SRCDIR="${CMAKE_CURRENT_SOURCE_DIR}/" 15 | LIBRARIES 16 | Qt::Gui 17 | Qt::Qml 18 | Qt::QmlPrivate 19 | Qt::Quick 20 | Qt::QuickPrivate 21 | TESTDATA ${test_data} 22 | ) 23 | 24 | if(QT_BUILD_STANDALONE_TESTS) 25 | qt_import_qml_plugins(tst_blendtrees) 26 | endif() 27 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | test01.qml 5 | test02.qml 6 | test03.qml 7 | test04.qml 8 | test05.qml 9 | test06.qml 10 | test07.qml 11 | test08.qml 12 | Circle.qml 13 | test09.qml 14 | test10.qml 15 | animate_color.cbor 16 | animate_vector3d.cbor 17 | animate_bool.cbor 18 | animate_real.cbor 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/timeline/blendtrees/doc/src/qtquicktimelineblendtrees-module-qml.qdoc: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only 3 | 4 | /*! 5 | \qmlmodule QtQuick.Timeline.BlendTrees 6 | \title Qt Quick Timeline Blend Trees QML Types 7 | \since 6.7 8 | 9 | \brief Provides QML types for blending multiple \l {TimelineAnimation}s 10 | together, creating new dynamic animations. 11 | 12 | //! [usage] 13 | To import the QML types into your application, use the following import 14 | statement in your .qml file: 15 | 16 | \qml 17 | import QtQuick.Timeline.BlendTrees 18 | \endqml 19 | //! [usage] 20 | */ 21 | -------------------------------------------------------------------------------- /dist/changes-6.0.0: -------------------------------------------------------------------------------- 1 | Qt 6.0.0 is a new major version release of Qt. It is not binary compatible with 2 | earlier Qt releases. 3 | 4 | The goal has been to retain as much source compatibility with Qt 5.15 as 5 | possible, but some changes were inevitable to make Qt a better framework. 6 | 7 | To make it easier to port to Qt 6.0, we have created a porting guide to 8 | summarize those changes and provide guidance to handle them. In the guide, you 9 | can find links to articles about changes that may affect your application and 10 | help you transition from Qt 5.15 to Qt 6.0: 11 | 12 | https://doc.qt.io/qt-6/portingguide.html 13 | 14 | For more details refer to the online documentation of Qt 6.0: 15 | 16 | https://doc.qt.io/qt-6/index.html 17 | 18 | -------------------------------------------------------------------------------- /src/timeline/blendtrees/qtquicktimelineblendtreesglobal_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QTQUICKTIMELINEBLENDTREESGLOBAL_P_H 5 | #define QTQUICKTIMELINEBLENDTREESGLOBAL_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include "qtquicktimelineblendtreesglobal.h" 19 | #include 20 | 21 | #endif // QTQUICKTIMELINEBLENDTREESGLOBAL_P_H 22 | -------------------------------------------------------------------------------- /src/timeline/doc/src/qt6-changes.qdoc: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only 3 | 4 | /*! 5 | \page qtquicktimeline-changes-qt6.html 6 | \title Changes to Qt Quick Timeline 7 | \ingroup changes-qt-5-to-6 8 | \brief Migrate Qt Quick Timeline to Qt 6. 9 | 10 | Qt 6 is a result of the conscious effort to make the framework more 11 | efficient and easy to use. 12 | 13 | We try to maintain binary and source compatibility for all the public 14 | APIs in each release. But some changes were inevitable in an effort to 15 | make Qt a better framework. 16 | 17 | When using the Qt Quick Timeline there are no code changes required when porting to Qt 6. 18 | */ 19 | -------------------------------------------------------------------------------- /dist/changes-5.14.0: -------------------------------------------------------------------------------- 1 | Qt 5.14 introduces many new features and improvements as well as bugfixes 2 | over the 5.13.x series. For more details, refer to the online documentation 3 | included in this distribution. The documentation is also available online: 4 | 5 | https://doc.qt.io/qt-5/index.html 6 | 7 | The Qt version 5.14 series is binary compatible with the 5.13.x series. 8 | Applications compiled for 5.13 will continue to run with 5.14. 9 | 10 | Some of the changes listed in this file include issue tracking numbers 11 | corresponding to tasks in the Qt Bug Tracker: 12 | 13 | https://bugreports.qt.io/ 14 | 15 | Each of these identifiers can be entered in the bug tracker to obtain more 16 | information about a particular change. 17 | 18 | - This release contains only minor code improvements. 19 | -------------------------------------------------------------------------------- /dist/changes-5.15.0: -------------------------------------------------------------------------------- 1 | Qt 5.15 introduces many new features and improvements as well as bugfixes 2 | over the 5.14.x series. For more details, refer to the online documentation 3 | included in this distribution. The documentation is also available online: 4 | 5 | https://doc.qt.io/qt-5/index.html 6 | 7 | The Qt version 5.15 series is binary compatible with the 5.14.x series. 8 | Applications compiled for 5.14 will continue to run with 5.15. 9 | 10 | Some of the changes listed in this file include issue tracking numbers 11 | corresponding to tasks in the Qt Bug Tracker: 12 | 13 | https://bugreports.qt.io/ 14 | 15 | Each of these identifiers can be entered in the bug tracker to obtain more 16 | information about a particular change. 17 | 18 | - This release contains only minor code improvements. 19 | -------------------------------------------------------------------------------- /dist/changes-5.14.1: -------------------------------------------------------------------------------- 1 | Qt 5.14.1 is a bug-fix release. It maintains both forward and backward 2 | compatibility (source and binary) with Qt 5.14.0. 3 | 4 | For more details, refer to the online documentation included in this 5 | distribution. The documentation is also available online: 6 | 7 | https://doc.qt.io/qt-5/index.html 8 | 9 | The Qt version 5.14 series is binary compatible with the 5.13.x series. 10 | Applications compiled for 5.13 will continue to run with 5.14. 11 | 12 | Some of the changes listed in this file include issue tracking numbers 13 | corresponding to tasks in the Qt Bug Tracker: 14 | 15 | https://bugreports.qt.io/ 16 | 17 | Each of these identifiers can be entered in the bug tracker to obtain more 18 | information about a particular change. 19 | 20 | - This release contains only minor code improvements. 21 | -------------------------------------------------------------------------------- /dist/changes-5.15.1: -------------------------------------------------------------------------------- 1 | Qt 5.15.1 is a bug-fix release. It maintains both forward and backward 2 | compatibility (source and binary) with Qt 5.15.0. 3 | 4 | For more details, refer to the online documentation included in this 5 | distribution. The documentation is also available online: 6 | 7 | https://doc.qt.io/qt-5/index.html 8 | 9 | The Qt version 5.15 series is binary compatible with the 5.14.x series. 10 | Applications compiled for 5.14 will continue to run with 5.15. 11 | 12 | Some of the changes listed in this file include issue tracking numbers 13 | corresponding to tasks in the Qt Bug Tracker: 14 | 15 | https://bugreports.qt.io/ 16 | 17 | Each of these identifiers can be entered in the bug tracker to obtain more 18 | information about a particular change. 19 | 20 | - This release contains only minor code improvements. 21 | -------------------------------------------------------------------------------- /dist/changes-5.14.2: -------------------------------------------------------------------------------- 1 | Qt 5.14.2 is a bug-fix release. It maintains both forward and backward 2 | compatibility (source and binary) with Qt 5.14.0 through 5.14.1. 3 | 4 | For more details, refer to the online documentation included in this 5 | distribution. The documentation is also available online: 6 | 7 | https://doc.qt.io/qt-5/index.html 8 | 9 | The Qt version 5.14 series is binary compatible with the 5.13.x series. 10 | Applications compiled for 5.13 will continue to run with 5.14. 11 | 12 | Some of the changes listed in this file include issue tracking numbers 13 | corresponding to tasks in the Qt Bug Tracker: 14 | 15 | https://bugreports.qt.io/ 16 | 17 | Each of these identifiers can be entered in the bug tracker to obtain more 18 | information about a particular change. 19 | 20 | - This release contains only minor code improvements. 21 | -------------------------------------------------------------------------------- /tests/auto/qtquicktimeline/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | # Generated from qtquicktimeline.pro. 5 | 6 | ##################################################################### 7 | ## tst_qtquicktimeline Test: 8 | ##################################################################### 9 | 10 | 11 | # Collect test data 12 | file(GLOB_RECURSE test_data 13 | RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} 14 | data/* 15 | ) 16 | 17 | qt_internal_add_test(tst_qtquicktimeline 18 | SOURCES 19 | tst_qtquicktimeline.cpp 20 | DEFINES 21 | SRCDIR="${CMAKE_CURRENT_SOURCE_DIR}/" 22 | LIBRARIES 23 | Qt::Gui 24 | Qt::Qml 25 | Qt::QmlPrivate 26 | Qt::Quick 27 | Qt::QuickPrivate 28 | TESTDATA ${test_data} 29 | ) 30 | 31 | if(QT_BUILD_STANDALONE_TESTS) 32 | qt_import_qml_plugins(tst_qtquicktimeline) 33 | endif() 34 | -------------------------------------------------------------------------------- /src/timeline/blendtrees/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | qt_internal_add_qml_module(QuickTimelineBlendTrees 5 | URI "QtQuick.Timeline.BlendTrees" 6 | VERSION "${PROJECT_VERSION}" 7 | DESIGNER_SUPPORTED 8 | CLASS_NAME QtQuickTimelineBlendTreesPlugin 9 | PLUGIN_TARGET qtquicktimelineblendtreesplugin 10 | DEPENDENCIES 11 | QtQuickTimeline 12 | SOURCES 13 | qblendtreenode.cpp qblendtreenode_p.h 14 | qtimelineanimationnode.cpp qtimelineanimationnode_p.h 15 | qblendanimationnode.cpp qblendanimationnode_p.h 16 | qtquicktimelineblendtreesglobal.h qtquicktimelineblendtreesglobal_p.h 17 | DEFINES 18 | QT_BUILD_QUICKTIMELINEBLENDTREES_LIB 19 | PUBLIC_LIBRARIES 20 | Qt::Core 21 | Qt::Qml 22 | Qt::Quick 23 | Qt::QuickTimeline 24 | LIBRARIES 25 | Qt::QuickPrivate 26 | Qt::QuickTimelinePrivate 27 | ) 28 | -------------------------------------------------------------------------------- /src/timeline/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | qt_internal_add_qml_module(QuickTimeline 5 | URI "QtQuick.Timeline" 6 | VERSION "${PROJECT_VERSION}" 7 | DESIGNER_SUPPORTED 8 | CLASS_NAME QtQuickTimelinePlugin 9 | PLUGIN_TARGET qtquicktimelineplugin 10 | DEPENDENCIES 11 | QtQuick 12 | SOURCES 13 | qquickkeyframedatautils_p.h 14 | qquickkeyframe.cpp qquickkeyframe_p.h 15 | qquicktimeline.cpp qquicktimeline_p.h 16 | qquicktimelineanimation.cpp qquicktimelineanimation_p.h 17 | qtquicktimelineglobal.h qtquicktimelineglobal_p.h 18 | DEFINES 19 | QT_BUILD_QUICK_TIMELINE_LIB 20 | PUBLIC_LIBRARIES 21 | Qt::Core 22 | Qt::Qml 23 | Qt::Quick 24 | LIBRARIES 25 | Qt::QuickPrivate 26 | ) 27 | 28 | qt_internal_add_docs(QuickTimeline 29 | doc/qtquicktimeline.qdocconf 30 | ) 31 | 32 | add_subdirectory(blendtrees) 33 | 34 | -------------------------------------------------------------------------------- /src/timeline/doc/src/qtquicktimeline-module-qml.qdoc: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only 3 | 4 | /*! 5 | \qmlmodule QtQuick.Timeline 1.0 6 | \title Qt Quick Timeline QML Types 7 | \ingroup qmlmodules 8 | \brief Provides QML types to use timelines and keyframes to animate Qt Quick 9 | user interfaces. 10 | 11 | \section1 QtQuick.Timeline import 12 | 13 | To import the QML types into your application, use the following import 14 | statement in your .qml file: 15 | 16 | \qml 17 | import QtQuick.Timeline 18 | \endqml 19 | 20 | \generatelist qmltypesbymodule QtQuick.Timeline 21 | 22 | 23 | \section1 QtQuick.Timeline.BlendTrees import 24 | 25 | The \BlendTrees submodule provides QML types for blending multiple 26 | \l {TimelineAnimation}s together, creating new dynamic animations. 27 | 28 | \include qtquicktimelineblendtrees-module-qml.qdoc usage 29 | 30 | \generatelist qmltypesbymodule QtQuick.Timeline.BlendTrees 31 | 32 | \noautolist 33 | */ 34 | -------------------------------------------------------------------------------- /tests/manual/keyframeBinaryGenerator/keyframeBinaryGenerator.pro: -------------------------------------------------------------------------------- 1 | QT += gui 2 | 3 | CONFIG += c++11 console 4 | CONFIG -= app_bundle 5 | 6 | # The following define makes your compiler emit warnings if you use 7 | # any Qt feature that has been marked deprecated (the exact warnings 8 | # depend on your compiler). Please consult the documentation of the 9 | # deprecated API in order to know how to port your code away from it. 10 | DEFINES += QT_DEPRECATED_WARNINGS 11 | 12 | # You can also make your code fail to compile if it uses deprecated APIs. 13 | # In order to do so, uncomment the following line. 14 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 15 | #DEFINES += QT_DISABLE_DEPRECATED_UP_TO=0x060000 # disables all APIs deprecated in Qt 6.0.0 and earlier 16 | 17 | SOURCES += \ 18 | main.cpp 19 | 20 | # Add 'timeline' path and include keyframedatautils 21 | INCLUDEPATH += $$PWD/../../../src/imports/timeline/ 22 | HEADERS += ../../../src/imports/timeline/keyframedatautils_p.h 23 | 24 | # Default rules for deployment. 25 | qnx: target.path = /tmp/$${TARGET}/bin 26 | else: unix:!android: target.path = /opt/$${TARGET}/bin 27 | !isEmpty(target.path): INSTALLS += target 28 | -------------------------------------------------------------------------------- /coin/axivion/ci_config_linux.json: -------------------------------------------------------------------------------- 1 | { 2 | "Project": { 3 | "BuildSystemIntegration": { 4 | "child_order": [ 5 | "GCCSetup", 6 | "CMake", 7 | "LinkLibraries" 8 | ] 9 | }, 10 | "CMake": { 11 | "_active": true, 12 | "_copy_from": "CMakeIntegration", 13 | "build_environment": {}, 14 | "build_options": "-j4", 15 | "generate_options": "--fresh", 16 | "generator": "Ninja" 17 | }, 18 | "GCCSetup": { 19 | "_active": true, 20 | "_copy_from": "Command", 21 | "build_command": "gccsetup --cc gcc --cxx g++ --config ../../../axivion/" 22 | }, 23 | "LinkLibraries": { 24 | "_active": true, 25 | "_copy_from": "AxivionLinker", 26 | "input_files": [ 27 | "build/lib/lib*.so*.ir", 28 | "build/qml/*/*/lib*.so*.ir" 29 | ], 30 | "ir": "build/$(env:TESTED_MODULE_COIN).ir" 31 | } 32 | }, 33 | "_Format": "1.0", 34 | "_Version": "7.6.2", 35 | "_VersionNum": [ 36 | 7, 37 | 6, 38 | 2, 39 | 12725 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /dist/changes-5.15.2: -------------------------------------------------------------------------------- 1 | Qt 5.15.2 is a bug-fix release. It maintains both forward and backward 2 | compatibility (source and binary) with Qt 5.15.1. 3 | 4 | For more details, refer to the online documentation included in this 5 | distribution. The documentation is also available online: 6 | 7 | https://doc.qt.io/qt-5.15/index.html 8 | 9 | The Qt version 5.15 series is binary compatible with the 5.14.x series. 10 | Applications compiled for 5.14 will continue to run with 5.15. 11 | 12 | Some of the changes listed in this file include issue tracking numbers 13 | corresponding to tasks in the Qt Bug Tracker: 14 | 15 | https://bugreports.qt.io/ 16 | 17 | Each of these identifiers can be entered in the bug tracker to obtain more 18 | information about a particular change. 19 | 20 | **************************************************************************** 21 | * Important Behavior Changes * 22 | **************************************************************************** 23 | 24 | **************************************************************************** 25 | * Library * 26 | **************************************************************************** 27 | 28 | 29 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | # Generated from qtquicktimeline.pro. 5 | 6 | cmake_minimum_required(VERSION 3.16) 7 | 8 | include(.cmake.conf) 9 | project(QtQuickTimeline # special case 10 | VERSION "${QT_REPO_MODULE_VERSION}" 11 | DESCRIPTION "Qt Quick Timeline Libraries" # special case 12 | HOMEPAGE_URL "https://qt.io/" 13 | LANGUAGES CXX C 14 | ) 15 | 16 | find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals) 17 | 18 | # This should be called as early as possible, just after find_package(BuildInternals) where it is 19 | # defined. 20 | qt_internal_project_setup() 21 | 22 | find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS Core) 23 | find_package(Qt6 ${PROJECT_VERSION} QUIET CONFIG OPTIONAL_COMPONENTS Quick) 24 | 25 | if(NOT TARGET Qt::Quick) 26 | message(NOTICE "Skipping the build as the condition \"TARGET Qt::Quick\" is not met.") 27 | return() 28 | endif() 29 | 30 | if(NOT QT_FEATURE_cborstreamreader OR NOT QT_FEATURE_cborstreamwriter) 31 | message(NOTICE "Skipping the build. QtQuickTimeline requires both 'cborstreamreader'" 32 | " and 'cborstreamwriter' features enabled in the Qt::Core module") 33 | return() 34 | endif() 35 | 36 | qt_build_repo() 37 | -------------------------------------------------------------------------------- /tests/auto/qtquicktimeline/data/deltafunction.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | width: 480 9 | height: 480 10 | 11 | Timeline { 12 | id: timeline 13 | 14 | objectName: "timeline" 15 | 16 | startFrame: 0 17 | endFrame: 100 18 | currentFrame: 0 19 | 20 | enabled: true 21 | 22 | animations: [ 23 | TimelineAnimation { 24 | objectName: "animation" 25 | id: animation 26 | duration: 200 27 | loops: 1 28 | from: 0 29 | to: 100 30 | running: false 31 | } 32 | 33 | ] 34 | 35 | KeyframeGroup { 36 | objectName: "group01" 37 | target: text 38 | property: "text" 39 | 40 | Keyframe { 41 | frame: 0 42 | value: "frame0" 43 | } 44 | 45 | Keyframe { 46 | frame: 50 47 | value: "frame50" 48 | } 49 | 50 | Keyframe { 51 | frame: 100 52 | value: "frame100" 53 | } 54 | } 55 | } 56 | 57 | Text { 58 | id: text 59 | objectName: "text" 60 | text: "no timeline" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/timeline/doc/src/qtquicktimeline-index.qdoc: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only 3 | 4 | /*! 5 | \page qtquicktimeline-index.html 6 | \title Qt Quick Timeline 7 | \brief Provides QML types to use timelines and keyframes to animate Qt Quick 8 | user interfaces. 9 | 10 | The Qt Quick Timeline module enables keyframe-based animations and 11 | parameterization. This module is directly supported by Qt Design Studio and 12 | Qt Quick Designer, with a timeline editor to create keyframe-based animations. 13 | 14 | \section1 Using the Module 15 | 16 | \include {module-use.qdocinc} {using the qml api} {QtQuick.Timeline} 17 | 18 | \section1 Articles and Guides 19 | 20 | \list 21 | \li \l{Qt Quick Timeline Overview}{Overview} 22 | \endlist 23 | 24 | \section1 Reference 25 | 26 | \list 27 | \li \l{Qt Quick Timeline QML Types}{QML Types} 28 | \endlist 29 | 30 | \section1 Module Evolution 31 | \l{Changes to Qt Quick Timeline} lists important changes in the 32 | module API and functionality that were done for the Qt 6 series of Qt. 33 | 34 | \section1 Licenses 35 | 36 | Qt Quick Timeline is available under commercial licenses from \l{The Qt Company}. 37 | In addition, it is available under the \l{GNU General Public License, version 3}. 38 | See \l{Qt Licensing} for further details. 39 | */ 40 | -------------------------------------------------------------------------------- /LICENSES/BSD-3-Clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) . 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 7 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /src/timeline/qquicktimelineanimation_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QQUICKTIMELINEANIMATION_H 5 | #define QQUICKTIMELINEANIMATION_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include "qtquicktimelineglobal_p.h" 19 | 20 | #include 21 | 22 | QT_BEGIN_NAMESPACE 23 | 24 | class Q_QUICKTIMELINE_EXPORT QQuickTimelineAnimation : public QQuickNumberAnimation 25 | { 26 | Q_OBJECT 27 | 28 | Q_PROPERTY(bool pingPong READ pingPong WRITE setPingPong NOTIFY pingPongChanged) 29 | QML_NAMED_ELEMENT(TimelineAnimation) 30 | QML_ADDED_IN_VERSION(1, 0) 31 | 32 | public: 33 | explicit QQuickTimelineAnimation(QObject *parent = nullptr); 34 | 35 | void setPingPong(bool b); 36 | bool pingPong() const; 37 | 38 | Q_SIGNALS: 39 | void pingPongChanged(); 40 | void finished(); 41 | 42 | private: 43 | void handleStarted(); 44 | void handleStopped(); 45 | 46 | bool m_pinpong = false; 47 | bool m_reversed = false; 48 | bool m_originalStart = true; 49 | int m_currentLoop = 0; 50 | int m_originalLoop = 0; 51 | }; 52 | 53 | QT_END_NAMESPACE 54 | 55 | #endif // QQUICKTIMELINEANIMATION_H 56 | -------------------------------------------------------------------------------- /src/timeline/blendtrees/qblendtreenode_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QBLENDTREENODE_P_H 5 | #define QBLENDTREENODE_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include "qtquicktimelineblendtreesglobal.h" 19 | 20 | #include 21 | #include 22 | 23 | QT_BEGIN_NAMESPACE 24 | 25 | class Q_QUICKTIMELINEBLENDTREES_EXPORT QBlendTreeNode : public QObject 26 | { 27 | Q_OBJECT 28 | Q_PROPERTY(bool outputEnabled READ outputEnabled WRITE setOutputEnabled NOTIFY outputEnabledChanged FINAL) 29 | QML_NAMED_ELEMENT(BlendTreeNode) 30 | QML_UNCREATABLE("Interface Class") 31 | public: 32 | explicit QBlendTreeNode(QObject *parent = nullptr); 33 | 34 | const QHash &frameData(); 35 | 36 | bool outputEnabled() const; 37 | void setOutputEnabled(bool isOutputEnabled); 38 | 39 | Q_SIGNALS: 40 | void frameDataChanged(); 41 | void outputEnabledChanged(); 42 | 43 | protected: 44 | QHash m_frameData; 45 | 46 | private Q_SLOTS: 47 | void handleFrameDataChanged(); 48 | 49 | private: 50 | bool m_outputEnabled = false; 51 | }; 52 | 53 | QT_END_NAMESPACE 54 | 55 | #endif // QBLENDTREENODE_P_H 56 | -------------------------------------------------------------------------------- /src/timeline/doc/src/qtquicktimeline-overview.qdoc: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2018 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only 3 | 4 | 5 | /*! 6 | \page qtquicktimeline-overview.html 7 | \title Qt Quick Timeline Overview 8 | \brief Describes using timelines in Qt Quick applications. 9 | 10 | Timelines can be used to animate items and to define their behavior. 11 | Only one timeline can be active at a particular point in time. 12 | 13 | Animating item properties enables their values to move through intermediate 14 | values instead of immediately changing to the target value. For example, 15 | to move an item in a scene, you can animate the properties that control 16 | the item's position, x and y, so that the item's position changes at 17 | keyframes on the way to the target position. Similarly, you could change 18 | the color and scale properties of the item at keyframes to make it appear 19 | to move closer or farther away. 20 | 21 | \QDS contains a timeline editor for creating keyframe based animations. 22 | 23 | \image {timeline-editor.png} {Timeline Editor in Qt Design Studio with keyframes} 24 | 25 | Qt Quick allows you to declare various UI states in \l State objects. 26 | These states are comprised of property changes from a base state, and 27 | can be a useful way of organizing your UI logic. Transitions are objects 28 | you can associate with an item to define how its properties will animate 29 | when they change due to a state change. You can bind timeline animations 30 | to states in \QDS. 31 | 32 | \image {timeline-settings.png} {Timeline settings panel in Qt Design Studio} 33 | */ 34 | -------------------------------------------------------------------------------- /tests/auto/qtquicktimeline/data/restorebindingtest.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 5 | import QtQuick.Timeline 6 | 7 | Item { 8 | Item { 9 | width: 480 10 | height: 480 11 | 12 | Timeline { 13 | id: timeline 14 | 15 | objectName: "timeline" 16 | 17 | startFrame: 0 18 | endFrame: 100 19 | currentFrame: 50 20 | 21 | enabled: false 22 | 23 | KeyframeGroup { 24 | objectName: "group01" 25 | target: rectangle 26 | property: "x" 27 | 28 | Keyframe { 29 | frame: 0 30 | value: 140 31 | } 32 | 33 | Keyframe { 34 | frame: 100 35 | value: rectangle.offset 36 | } 37 | } 38 | 39 | KeyframeGroup { 40 | target: rectangle 41 | property: "anchors.topMargin" 42 | 43 | Keyframe { 44 | frame: 0 45 | value: rectangle.offset 46 | } 47 | 48 | Keyframe { 49 | frame: 100 50 | value: 140 51 | } 52 | } 53 | } 54 | 55 | Rectangle { 56 | id: rectangle 57 | objectName: "rectangle" 58 | property int offset: 0 59 | 60 | x: offset 61 | width: 20 62 | height: 20 63 | color: "red" 64 | anchors.top: parent.top 65 | anchors.topMargin: offset 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/manual/keyframeBinaryGenerator/keyframes_file_specification.txt: -------------------------------------------------------------------------------- 1 | 2 | This document explains the format of QQuickTimeline binary keyframes. 3 | Timeline KeyframeGroup 'keyframeSource' property can be used to load 4 | these keyframes into KeyframeGroup. 5 | 6 | Binary format is using CBOR binary data serialization format: https://cbor.io 7 | 8 | The format of keyframes CBOR data is following: 9 | 10 | [ 11 | "QTimelineKeyframes", // string 12 | version, // integer 13 | propertyType, // integer 14 | [ 15 | frame, // double 16 | easing, // integer 17 | [n items depending on propertyType] 18 | frame, 19 | ... 20 | ] 21 | ] 22 | 23 | The content of keyframes array depend on propertyType. So for qreal 24 | each dataset is 3 numbers (frame + easing + qreal), for QVector3D 25 | 5 numbers (frame + easing + QVector3D) and for QQuartenion 26 | 6 numbers (frame + easing + QQuartenion). 27 | 28 | Value of propertyType is the QMetaType::Type of the keyframe property. 29 | 30 | Value of frame (time) is double. 31 | 32 | Value of easing is the QEasingCurve::Type of the keyframe easing. 33 | 34 | 35 | Example 1. CBOR content for qreal property (e.g. Item opacity): 36 | 37 | [ 38 | "QTimelineKeyframes", 39 | 1, 40 | 6, 41 | [ 42 | 0, 43 | 0, 44 | 0.0, 45 | 500, 46 | 0, 47 | 0.2, 48 | 1000, 49 | 0, 50 | 1.0 51 | ] 52 | ] 53 | 54 | 55 | Example 2. CBOR content for QVector3D property (e.g. Node position): 56 | 57 | [ 58 | "QTimelineKeyframes", 59 | 1, 60 | 83, 61 | [ 62 | 0, 63 | 0, 64 | 0.123, 65 | 0.123, 66 | 0.123, 67 | 10, 68 | 0, 69 | 0.234, 70 | 0.123, 71 | 0.123, 72 | 20, 73 | 0, 74 | 0.334, 75 | 0.334, 76 | 0.334, 77 | ... 78 | ] 79 | ] 80 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[annotations]] 4 | path = ["tests/**.cbor", "tests/**.txt"] 5 | precedence = "closest" 6 | SPDX-FileCopyrightText = "Copyright (C) The Qt Company Ltd." 7 | SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GPL-3.0-only" 8 | 9 | [[annotations]] 10 | path = ["**.pro", "**.qrc", ".cmake.conf", "**.yaml", "coin/axivion/ci_config_linux.json", 11 | ".tag"] 12 | precedence = "closest" 13 | comment = "build system" 14 | SPDX-FileCopyrightText = "Copyright (C) The Qt Company Ltd." 15 | SPDX-License-Identifier = "BSD-3-Clause" 16 | 17 | [[annotations]] 18 | path = ["**/.gitattributes", "**.gitignore", "**.gitreview"] 19 | precedence = "closest" 20 | SPDX-FileCopyrightText = "Copyright (C) The Qt Company Ltd." 21 | SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR BSD-3-Clause" 22 | 23 | [[annotations]] 24 | path = ["**/doc/images/**", "**.qdocconf", "src/timeline/doc/style/style.css"] 25 | comment = "documentation" 26 | precedence = "closest" 27 | SPDX-FileCopyrightText = "Copyright (C) The Qt Company Ltd." 28 | SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only" 29 | 30 | [[annotations]] 31 | path = ["**.toml", "licenseRule.json"] 32 | precedence = "override" 33 | SPDX-FileCopyrightText = "Copyright (C) The Qt Company Ltd." 34 | SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR BSD-3-Clause" 35 | 36 | [[annotations]] 37 | path = ["**/qt_attribution.json"] 38 | precedence = "override" 39 | SPDX-FileCopyrightText = "Copyright (C) The Qt Company Ltd." 40 | SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only" 41 | 42 | [[annotations]] 43 | path = ["**LICENSE*"] 44 | precedence = "override" 45 | comment = "License file." 46 | SPDX-FileCopyrightText = "None" 47 | SPDX-License-Identifier = "CC0-1.0" 48 | 49 | -------------------------------------------------------------------------------- /tests/auto/qtquicktimeline/data/vectors.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | id: item1 9 | 10 | Timeline { 11 | objectName: "timeline" 12 | id: timeline 13 | enabled: true 14 | 15 | startFrame: 0 16 | endFrame: 200 17 | currentFrame: 0 18 | KeyframeGroup { 19 | objectName: "group01" 20 | target: rotation 21 | property: "axis.x" 22 | Keyframe { 23 | frame: 0 24 | value: 0 25 | } 26 | 27 | Keyframe { 28 | frame: 50 29 | value: 1 30 | } 31 | Keyframe { 32 | frame: 100 33 | value: 0 34 | } 35 | } 36 | 37 | KeyframeGroup { 38 | target: rotation 39 | property: "origin.x" 40 | Keyframe { 41 | frame: 0 42 | value: 0 43 | } 44 | 45 | Keyframe { 46 | frame: 50 47 | value: 10 48 | } 49 | Keyframe { 50 | frame: 100 51 | value: 20 52 | } 53 | } 54 | 55 | } 56 | 57 | Text { 58 | id: rectangle 59 | x: 220 60 | y: 140 61 | width: 300 62 | height: 300 63 | transform: Rotation { 64 | id: rotation 65 | objectName: "rotation" 66 | origin.x: 30 67 | origin.y: 30 68 | axis.x: 0 69 | axis.y: 1 70 | axis.z: 0 71 | angle: 18 72 | } 73 | 74 | 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/timeline/blendtrees/qblendanimationnode_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QBLENDANIMATIONNODE_P_H 5 | #define QBLENDANIMATIONNODE_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | QT_BEGIN_NAMESPACE 24 | 25 | class Q_QUICKTIMELINEBLENDTREES_EXPORT QBlendAnimationNode : public QBlendTreeNode 26 | { 27 | Q_OBJECT 28 | Q_PROPERTY(QBlendTreeNode *source1 READ source1 WRITE setSource1 NOTIFY source1Changed FINAL) 29 | Q_PROPERTY(QBlendTreeNode *source2 READ source2 WRITE setSource2 NOTIFY source2Changed FINAL) 30 | Q_PROPERTY(qreal weight READ weight WRITE setWeight NOTIFY weightChanged FINAL) 31 | QML_NAMED_ELEMENT(BlendAnimationNode) 32 | public: 33 | explicit QBlendAnimationNode(QObject *parent = nullptr); 34 | 35 | QBlendTreeNode *source1() const; 36 | void setSource1(QBlendTreeNode *newSource1); 37 | 38 | QBlendTreeNode *source2() const; 39 | void setSource2(QBlendTreeNode *newSource2); 40 | 41 | qreal weight() const; 42 | void setWeight(qreal newWeight); 43 | 44 | private Q_SLOTS: 45 | void handleInputFrameDataChanged(); 46 | 47 | Q_SIGNALS: 48 | void source1Changed(); 49 | void source2Changed(); 50 | void weightChanged(); 51 | 52 | private: 53 | QBlendTreeNode *m_source1 = nullptr; 54 | QBlendTreeNode *m_source2 = nullptr; 55 | qreal m_weight = 0.5; 56 | 57 | QMetaObject::Connection m_source1OutputConnection; 58 | QMetaObject::Connection m_source2OutputConnection; 59 | QMetaObject::Connection m_source1DestroyedConnection; 60 | QMetaObject::Connection m_source2DestroyedConnection; 61 | }; 62 | 63 | QT_END_NAMESPACE 64 | 65 | #endif // QBLENDANIMATIONNODE_P_H 66 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/test03.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | id: item1 9 | 10 | Timeline { 11 | id: timeline 12 | enabled: true 13 | 14 | startFrame: 0 15 | endFrame: 200 16 | currentFrame: input.text 17 | KeyframeGroup { 18 | target: needle 19 | property: "rotation" 20 | Keyframe { 21 | frame: 0 22 | value: 0 23 | } 24 | 25 | Keyframe { 26 | frame: 100 27 | value: 90 28 | } 29 | Keyframe { 30 | frame: 200 31 | value: 180 32 | } 33 | } 34 | 35 | KeyframeGroup { 36 | target: needle 37 | property: "color" 38 | Keyframe { 39 | frame: 0 40 | value: "blue" 41 | } 42 | 43 | Keyframe { 44 | frame: 100 45 | value: "green" 46 | } 47 | Keyframe { 48 | frame: 200 49 | value: "red" 50 | } 51 | } 52 | 53 | } 54 | 55 | Rectangle { 56 | id: rectangle 57 | x: 220 58 | y: 140 59 | width: 300 60 | height: 300 61 | color: "#000000" 62 | radius: 150 63 | anchors.horizontalCenter: parent.horizontalCenter 64 | anchors.verticalCenter: parent.verticalCenter 65 | 66 | Rectangle { 67 | id: needle 68 | x: 0 69 | y: 148 70 | width: 150 71 | height: 4 72 | color: "#c41616" 73 | transformOrigin: Item.Right 74 | } 75 | } 76 | 77 | TextInput { 78 | id: input 79 | x: 207 80 | y: 392 81 | width: 227 82 | height: 65 83 | text: "10" 84 | anchors.horizontalCenter: parent.horizontalCenter 85 | horizontalAlignment: Text.AlignHCenter 86 | font.pointSize: 14 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/timeline/blendtrees/qtimelineanimationnode_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QTIMELINEANIMATIONNODE_P_H 5 | #define QTIMELINEANIMATIONNODE_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | QT_BEGIN_NAMESPACE 25 | 26 | class Q_QUICKTIMELINEBLENDTREES_EXPORT QTimelineAnimationNode : public QBlendTreeNode 27 | { 28 | Q_OBJECT 29 | Q_PROPERTY(QQuickTimelineAnimation *animation READ animation WRITE setAnimation NOTIFY animationChanged FINAL) 30 | Q_PROPERTY(QQuickTimeline *timeline READ timeline WRITE setTimeline NOTIFY timelineChanged FINAL) 31 | Q_PROPERTY(qreal currentFrame READ currentFrame WRITE setCurrentFrame NOTIFY currentFrameChanged FINAL) 32 | QML_NAMED_ELEMENT(TimelineAnimationNode) 33 | public: 34 | explicit QTimelineAnimationNode(QObject *parent = nullptr); 35 | 36 | QQuickTimelineAnimation *animation() const; 37 | void setAnimation(QQuickTimelineAnimation *newAnimation); 38 | 39 | QQuickTimeline *timeline() const; 40 | void setTimeline(QQuickTimeline *newTimeline); 41 | 42 | qreal currentFrame() const; 43 | void setCurrentFrame(qreal newCurrentFrame); 44 | 45 | Q_SIGNALS: 46 | void animationChanged(); 47 | void timelineChanged(); 48 | void currentFrameChanged(); 49 | 50 | private: 51 | void updateFrameData(); 52 | void updateAnimationTarget(); 53 | QQuickTimelineAnimation *m_animation = nullptr; 54 | QQuickTimeline *m_timeline = nullptr; 55 | qreal m_currentFrame = -1.0; 56 | 57 | QMetaObject::Connection m_animationDestroyedConnection; 58 | QMetaObject::Connection m_timelineDestroyedConnection; 59 | }; 60 | 61 | QT_END_NAMESPACE 62 | 63 | #endif // QTIMELINEANIMATIONNODE_P_H 64 | -------------------------------------------------------------------------------- /src/timeline/doc/qtquicktimeline.qdocconf: -------------------------------------------------------------------------------- 1 | include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) 2 | 3 | project = QtQuickTimeline 4 | description = Qt Quick Timeline Reference Documentation 5 | version = $QT_VERSION 6 | buildversion = Qt Quick Timeline | Commercial or GPLv3 7 | 8 | examplesinstallpath = qtquicktimeline 9 | 10 | qhp.projects = QtQuickTimeline 11 | 12 | qhp.QtQuickTimeline.file = qtquicktimeline.qhp 13 | qhp.QtQuickTimeline.namespace = org.qt-project.qtquicktimeline.$QT_VERSION_TAG 14 | qhp.QtQuickTimeline.virtualFolder = qtquicktimeline 15 | qhp.QtQuickTimeline.indexTitle = Qt Quick Timeline 16 | qhp.QtQuickTimeline.indexRoot = 17 | 18 | qhp.QtQuickTimeline.subprojects = manual qmltypes 19 | 20 | qhp.QtQuickTimeline.subprojects.qmltypes.title = QML Types 21 | qhp.QtQuickTimeline.subprojects.qmltypes.indexTitle = Qt Quick Timeline QML Types 22 | qhp.QtQuickTimeline.subprojects.qmltypes.selectors = qmlclass 23 | qhp.QtQuickTimeline.subprojects.qmltypes.sortPages = true 24 | 25 | #qhp.QtQuickTimeline.subprojects.examples.title = Examples 26 | #qhp.QtQuickTimeline.subprojects.examples.indexTitle = Qt Qt Quick Timeline Examples 27 | #qhp.QtQuickTimeline.subprojects.examples.selectors = example 28 | #qhp.QtQuickTimeline.subprojects.examples.sortPages = true 29 | 30 | qhp.QtQuickTimeline.subprojects.manual.title = Qt Quick Timeline 31 | qhp.QtQuickTimeline.subprojects.manual.indexTitle = Qt Quick Timeline module topics 32 | qhp.QtQuickTimeline.subprojects.manual.type = manual 33 | 34 | headerdirs += .. 35 | sourcedirs += .. 36 | #exampledirs = 37 | imagedirs += images \ 38 | 39 | depends += qtcore qtdoc qtqml qtquick 40 | 41 | tagfile = qtquicktimeline.tags 42 | 43 | #add generic thumbnail images for example documentation that does not have an image. 44 | #manifestmeta.thumbnail.names += 45 | 46 | navigation.landingpage = "Qt Quick Timeline" 47 | navigation.qmltypespage = "Qt Quick Timeline QML Types" 48 | # Auto-generate navigation linking based on "Qt Quick Timeline module topics": 49 | navigation.toctitles = "Qt Quick Timeline module topics" 50 | navigation.toctitles.inclusive = false 51 | 52 | # Allow zero warnings when testing documentation in CI 53 | warninglimit = 0 54 | -------------------------------------------------------------------------------- /tests/auto/qtquicktimeline/data/parameterization.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | id: item1 9 | 10 | Timeline { 11 | objectName: "timeline" 12 | id: timeline 13 | enabled: true 14 | 15 | startFrame: 0 16 | endFrame: 200 17 | currentFrame: input.text 18 | KeyframeGroup { 19 | objectName: "group01" 20 | target: needle 21 | property: "rotation" 22 | Keyframe { 23 | frame: 0 24 | value: 0 25 | } 26 | 27 | Keyframe { 28 | frame: 100 29 | value: 90 30 | } 31 | Keyframe { 32 | frame: 200 33 | value: 180 34 | } 35 | } 36 | 37 | KeyframeGroup { 38 | target: needle 39 | property: "color" 40 | Keyframe { 41 | frame: 0 42 | value: "blue" 43 | } 44 | 45 | Keyframe { 46 | frame: 100 47 | value: "green" 48 | } 49 | Keyframe { 50 | frame: 200 51 | value: "red" 52 | } 53 | } 54 | 55 | } 56 | 57 | Rectangle { 58 | id: rectangle 59 | x: 220 60 | y: 140 61 | width: 300 62 | height: 300 63 | color: "#000000" 64 | radius: 150 65 | anchors.horizontalCenter: parent.horizontalCenter 66 | anchors.verticalCenter: parent.verticalCenter 67 | 68 | Rectangle { 69 | objectName: "needle" 70 | id: needle 71 | x: 0 72 | y: 148 73 | width: 150 74 | height: 4 75 | color: "#c41616" 76 | transformOrigin: Item.Right 77 | } 78 | } 79 | 80 | TextInput { 81 | objectName: "textInput" 82 | id: input 83 | x: 207 84 | y: 392 85 | width: 227 86 | height: 65 87 | text: "10" 88 | anchors.horizontalCenter: parent.horizontalCenter 89 | horizontalAlignment: Text.AlignHCenter 90 | font.pointSize: 14 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/timeline/blendtrees/qblendtreenode.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include "qblendtreenode_p.h" 5 | 6 | QT_BEGIN_NAMESPACE 7 | 8 | /*! 9 | \qmltype BlendTreeNode 10 | \nativetype QBlendTreeNode 11 | \inqmlmodule QtQuick.Timeline.BlendTrees 12 | \ingroup qtqmltypes 13 | 14 | \brief Base class for all blend tree nodes. 15 | 16 | BlendTreeNode is the base class for all blend tree nodes. It is not 17 | intended to be used directly, but rather to be subclassed to create 18 | custom blend tree nodes. 19 | */ 20 | 21 | /*! 22 | \qmlproperty bool BlendTreeNode::outputEnabled 23 | 24 | This property determines whether the blend tree node should commit 25 | the property changes. The default value is /c false. 26 | 27 | Any node can be an output node which commits the property changes, 28 | but it should usually be the last node in the blend tree. If multiple 29 | nodes are outputting data and there is a conflict, then the last 30 | property change will be used. So ideally if there are multiple output 31 | nodes in a tree, the targets and properties effected should be disjoint. 32 | */ 33 | 34 | QBlendTreeNode::QBlendTreeNode(QObject *parent) 35 | : QObject{parent} 36 | { 37 | connect(this, &QBlendTreeNode::frameDataChanged, this, &QBlendTreeNode::handleFrameDataChanged); 38 | connect(this, &QBlendTreeNode::outputEnabledChanged, this, &QBlendTreeNode::handleFrameDataChanged); 39 | } 40 | 41 | const QHash &QBlendTreeNode::frameData() 42 | { 43 | return m_frameData; 44 | } 45 | 46 | bool QBlendTreeNode::outputEnabled() const 47 | { 48 | return m_outputEnabled; 49 | } 50 | 51 | void QBlendTreeNode::setOutputEnabled(bool isOutputEnabled) 52 | { 53 | if (m_outputEnabled == isOutputEnabled) 54 | return; 55 | m_outputEnabled = isOutputEnabled; 56 | Q_EMIT outputEnabledChanged(); 57 | } 58 | 59 | void QBlendTreeNode::handleFrameDataChanged() 60 | { 61 | // If we are not outputting data, then there is nothing to do 62 | if (!m_outputEnabled) 63 | return; 64 | 65 | // Commit the property 66 | for (auto it = m_frameData.cbegin(), end = m_frameData.cend(); it != end; ++it) { 67 | const auto &property = it.key(); 68 | property.write(it.value()); 69 | } 70 | } 71 | 72 | QT_END_NAMESPACE 73 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/test07.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | id: item1 9 | width: 640 10 | height: 480 11 | 12 | Text { 13 | id: text1 14 | x: 70 15 | y: 93 16 | text: "Count 01" 17 | font.pixelSize: 12 18 | } 19 | 20 | Timeline { 21 | id: timeline 22 | enabled: true 23 | endFrame: 1000 24 | startFrame: 0 25 | 26 | KeyframeGroup { 27 | target: text1 28 | property: "text" 29 | 30 | Keyframe { 31 | frame: 0 32 | value: "Count 01" 33 | } 34 | 35 | Keyframe { 36 | frame: 100 37 | value: "Count 10" 38 | } 39 | 40 | Keyframe { 41 | frame: 200 42 | value: "Count 20" 43 | } 44 | 45 | Keyframe { 46 | frame: 300 47 | value: "Count 30" 48 | } 49 | 50 | Keyframe { 51 | frame: 400 52 | value: "Count 40" 53 | } 54 | 55 | Keyframe { 56 | frame: 500 57 | value: "Count 50" 58 | } 59 | 60 | Keyframe { 61 | frame: 600 62 | value: "Count 60" 63 | } 64 | 65 | Keyframe { 66 | frame: 700 67 | value: "Count 70" 68 | } 69 | 70 | Keyframe { 71 | frame: 800 72 | value: "Count 80" 73 | } 74 | 75 | Keyframe { 76 | frame: 900 77 | value: "Count 90" 78 | } 79 | 80 | Keyframe { 81 | frame: 1000 82 | value: "Count 100" 83 | } 84 | } 85 | } 86 | 87 | NumberAnimation { 88 | id: numberAnimation 89 | target: timeline 90 | property: "currentFrame" 91 | running: true 92 | to: timeline.endFrame 93 | duration: 1000 94 | from: timeline.startFrame 95 | loops: -1 96 | } 97 | } 98 | 99 | /*##^## Designer { 100 | D{i:1;timeline_expanded:true}D{i:2;currentFrame__AT__NodeInstance:1} 101 | } 102 | ##^##*/ 103 | -------------------------------------------------------------------------------- /src/timeline/qquicktimeline_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QQUICKTIMELINE_P_H 5 | #define QQUICKTIMELINE_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include "qquickkeyframe_p.h" 19 | #include "qquicktimelineanimation_p.h" 20 | #include "qtquicktimelineglobal_p.h" 21 | 22 | #include 23 | 24 | QT_BEGIN_NAMESPACE 25 | 26 | class QQuickTimelinePrivate; 27 | 28 | class Q_QUICKTIMELINE_EXPORT QQuickTimeline : public QObject, public QQmlParserStatus 29 | { 30 | Q_OBJECT 31 | Q_DECLARE_PRIVATE(QQuickTimeline) 32 | 33 | Q_INTERFACES(QQmlParserStatus) 34 | 35 | Q_PROPERTY(qreal startFrame READ startFrame WRITE setStartFrame NOTIFY startFrameChanged) 36 | Q_PROPERTY(qreal endFrame READ endFrame WRITE setEndFrame NOTIFY endFrameChanged) 37 | Q_PROPERTY(qreal currentFrame READ currentFrame WRITE setCurrentFrame NOTIFY currentFrameChanged) 38 | Q_PROPERTY(QQmlListProperty keyframeGroups READ keyframeGroups) 39 | Q_PROPERTY(QQmlListProperty animations READ animations) 40 | Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) 41 | 42 | QML_NAMED_ELEMENT(Timeline) 43 | QML_ADDED_IN_VERSION(1, 0) 44 | 45 | Q_CLASSINFO("DefaultProperty", "keyframeGroups") 46 | 47 | public: 48 | explicit QQuickTimeline(QObject *parent = nullptr); 49 | 50 | QQmlListProperty keyframeGroups(); 51 | QQmlListProperty animations(); 52 | 53 | bool enabled() const; 54 | void setEnabled(bool enabled); 55 | 56 | qreal startFrame() const; 57 | void setStartFrame(qreal); 58 | 59 | qreal endFrame() const; 60 | void setEndFrame(qreal); 61 | 62 | qreal currentFrame() const; 63 | void setCurrentFrame(qreal); 64 | 65 | void reevaluate(); 66 | 67 | void init(); 68 | void reset(); 69 | 70 | QList getAnimations() const; 71 | 72 | protected: 73 | void classBegin() override; 74 | void componentComplete() override; 75 | 76 | Q_SIGNALS: 77 | void enabledChanged(); 78 | void startFrameChanged(); 79 | void endFrameChanged(); 80 | void currentFrameChanged(); 81 | }; 82 | 83 | QT_END_NAMESPACE 84 | 85 | #endif // QQUICKTIMELINE_P_H 86 | -------------------------------------------------------------------------------- /tests/auto/qtquicktimeline/data/simpletest.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | Item { 9 | width: 480 10 | height: 480 11 | 12 | Timeline { 13 | id: timeline 14 | 15 | objectName: "timeline" 16 | 17 | startFrame: 0 18 | endFrame: 100 19 | currentFrame: 50 20 | 21 | enabled: true 22 | 23 | animations: [ 24 | TimelineAnimation { 25 | objectName: "animation" 26 | id: animation 27 | duration: 200 28 | loops: 1 29 | from: 0 30 | to: 100 31 | running: false 32 | } 33 | 34 | ] 35 | 36 | KeyframeGroup { 37 | objectName: "group01" 38 | target: rectangle 39 | property: "x" 40 | 41 | Keyframe { 42 | frame: 0 43 | value: 0 44 | } 45 | 46 | Keyframe { 47 | objectName: "keyframe" 48 | frame: 50 49 | value: 100 50 | } 51 | 52 | Keyframe { 53 | frame: 100 54 | value: 200 55 | } 56 | } 57 | 58 | KeyframeGroup { 59 | target: rectangle 60 | property: "y" 61 | 62 | Keyframe { 63 | frame: 0 64 | value: 0 65 | } 66 | 67 | Keyframe { 68 | frame: 50 69 | value: 100 70 | } 71 | 72 | Keyframe { 73 | objectName: "easingBounce" 74 | frame: 100 75 | value: 200 76 | easing.type: Easing.InBounce 77 | } 78 | } 79 | 80 | KeyframeGroup { 81 | target: rectangle 82 | property: "color" 83 | 84 | Keyframe { 85 | frame: 0 86 | value: "red" 87 | } 88 | 89 | Keyframe { 90 | frame: 50 91 | value: "blue" 92 | } 93 | 94 | Keyframe { 95 | frame: 100 96 | value: "yellow" 97 | } 98 | } 99 | } 100 | 101 | Rectangle { 102 | id: rectangle 103 | 104 | objectName: "rectangle" 105 | 106 | width: 20 107 | height: 20 108 | color: "red" 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/timeline/doc/style/style.css: -------------------------------------------------------------------------------- 1 | a:link, a:visited { 2 | color: #00732F; 3 | text-decoration: none; 4 | font-weight: bold; 5 | } 6 | 7 | body { 8 | font: normal 400 14px/1.2 Arial; 9 | margin-top: 85px; 10 | } 11 | 12 | h1 { 13 | margin: 0; 14 | } 15 | 16 | h2 { 17 | font: 500 20px/1.2 Arial; 18 | } 19 | 20 | h3.fn, span.fn { 21 | -moz-border-radius: 7px 7px 7px 7px; 22 | -webkit-border-radius: 7px 7px 7px 7px; 23 | border-radius: 7px 7px 7px 7px; 24 | background-color: #F6F6F6; 25 | border-width: 1px; 26 | border-style: solid; 27 | border-color: #E6E6E6; 28 | word-spacing: 3px; 29 | padding: 3px 5px; 30 | } 31 | 32 | table, pre { 33 | -moz-border-radius: 7px 7px 7px 7px; 34 | -webkit-border-radius: 7px 7px 7px 7px; 35 | border-radius: 7px 7px 7px 7px; 36 | background-color: #F6F6F6; 37 | border: 1px solid #E6E6E6; 38 | border-collapse: separate; 39 | font-size: 12px; 40 | line-height: 1.2; 41 | margin-bottom: 25px; 42 | margin-left: 15px; 43 | } 44 | 45 | table td { 46 | padding: 3px 15px 3px 20px; 47 | } 48 | 49 | table tr.even { 50 | background-color: white; 51 | color: #66666E; 52 | } 53 | 54 | table tr.odd { 55 | background-color: #F6F6F6; 56 | color: #66666E; 57 | } 58 | 59 | li { 60 | margin-bottom: 10px; 61 | padding-left: 12px; 62 | } 63 | 64 | .cpp { 65 | display: block; 66 | margin: 10; 67 | overflow: hidden; 68 | overflow-x: hidden; 69 | overflow-y: hidden; 70 | padding: 20px 0 20px 0; 71 | } 72 | 73 | .footer { 74 | margin-top: 50px; 75 | } 76 | 77 | .memItemLeft { 78 | padding-right: 3px; 79 | } 80 | 81 | .memItemRight { 82 | padding: 3px 15px 3px 0; 83 | } 84 | 85 | .qml { 86 | display: block; 87 | margin: 10; 88 | overflow: hidden; 89 | overflow-x: hidden; 90 | overflow-y: hidden; 91 | padding: 20px 0 20px 0; 92 | } 93 | 94 | .qmldefault { 95 | padding-left: 5px; 96 | float: right; 97 | color: red; 98 | } 99 | 100 | .qmlreadonly { 101 | padding-left: 5px; 102 | float: right; 103 | color: #254117; 104 | } 105 | 106 | .rightAlign { 107 | padding: 3px 5px 3px 10px; 108 | text-align: right; 109 | } 110 | 111 | .title { 112 | background-color: white; 113 | color: #44A51C; 114 | font-family: Verdana; 115 | font-size: 35px; 116 | font-weight: normal; 117 | left: 0; 118 | padding-bottom: 5px; 119 | padding-left: 16px; 120 | padding-top: 20px; 121 | position: absolute; 122 | right: 0; 123 | top: 0; 124 | } 125 | 126 | .toc { 127 | float: right; 128 | -moz-border-radius: 7px 7px 7px 7px; 129 | -webkit-border-radius: 7px 7px 7px 7px; 130 | border-radius: 7px 7px 7px 7px; 131 | background-color: #F6F6F6; 132 | border: 1px solid #DDD; 133 | margin: 0 20px 10px 10px; 134 | padding: 20px 15px 20px 20px; 135 | height: auto; 136 | width: 200px; 137 | } 138 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/test01.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | Rectangle { 9 | width: 40 10 | height: 40 11 | color: "blue" 12 | MouseArea { 13 | anchors.fill: parent 14 | onClicked: timeline.enabled = !timeline.enabled 15 | } 16 | } 17 | 18 | Rectangle { 19 | x: 80 20 | width: 40 21 | height: 40 22 | color: "blue" 23 | MouseArea { 24 | anchors.fill: parent 25 | onClicked: animation.restart() 26 | } 27 | } 28 | 29 | Item { 30 | width: 480 31 | height: 480 32 | 33 | Timeline { 34 | id: timeline 35 | 36 | startFrame: 0 37 | endFrame: 100 38 | currentFrame: 50 39 | 40 | enabled: true 41 | 42 | animations: [ 43 | TimelineAnimation { 44 | id: animation 45 | duration: 2000 46 | from: 0 47 | to: 100 48 | running: false 49 | } 50 | 51 | ] 52 | 53 | KeyframeGroup { 54 | target: rectangle 55 | property: "x" 56 | 57 | Keyframe { 58 | frame: 0 59 | value: 0 60 | } 61 | 62 | Keyframe { 63 | frame: 50 64 | value: 100 65 | } 66 | 67 | Keyframe { 68 | frame: 100 69 | value: 200 70 | } 71 | } 72 | 73 | KeyframeGroup { 74 | target: rectangle 75 | property: "y" 76 | 77 | Keyframe { 78 | frame: 0 79 | value: 0 80 | } 81 | 82 | Keyframe { 83 | frame: 50 84 | value: 100 85 | easing.type: Easing.InBounce 86 | } 87 | 88 | Keyframe { 89 | frame: 100 90 | value: 200 91 | } 92 | } 93 | 94 | KeyframeGroup { 95 | target: rectangle 96 | property: "color" 97 | 98 | Keyframe { 99 | frame: 0 100 | value: "red" 101 | } 102 | 103 | Keyframe { 104 | frame: 50 105 | value: "blue" 106 | } 107 | 108 | Keyframe { 109 | frame: 100 110 | value: "yellow" 111 | } 112 | } 113 | } 114 | 115 | Rectangle { 116 | id: rectangle 117 | width: 20 118 | height: 20 119 | color: "red" 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/main.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.6 5 | import QtQuick.Window 2.2 6 | import QtQuick.Timeline 1.0 7 | 8 | Window { 9 | visible: true 10 | width: 640 11 | height: 480 12 | title: qsTr("Hello World") 13 | 14 | Loader { 15 | id: loader 16 | anchors.fill: parent 17 | } 18 | 19 | Row { 20 | anchors.bottom: parent.bottom 21 | anchors.bottomMargin: 10 22 | spacing: 4 23 | Text { 24 | text: "Test 01" 25 | font.pixelSize: 12 26 | MouseArea { 27 | anchors.fill: parent 28 | onClicked: loader.source = "test01.qml" 29 | } 30 | } 31 | 32 | Text { 33 | text: "Test 02" 34 | font.pixelSize: 12 35 | MouseArea { 36 | anchors.fill: parent 37 | onClicked: loader.source = "test02.qml" 38 | } 39 | } 40 | 41 | Text { 42 | text: "Test 03" 43 | font.pixelSize: 12 44 | MouseArea { 45 | anchors.fill: parent 46 | onClicked: loader.source = "test03.qml" 47 | } 48 | } 49 | 50 | Text { 51 | text: "Test 04" 52 | font.pixelSize: 12 53 | MouseArea { 54 | anchors.fill: parent 55 | onClicked: loader.source = "test04.qml" 56 | } 57 | } 58 | 59 | Text { 60 | text: "Test 05" 61 | font.pixelSize: 12 62 | MouseArea { 63 | anchors.fill: parent 64 | onClicked: loader.source = "test05.qml" 65 | } 66 | } 67 | 68 | Text { 69 | text: "Test 06" 70 | font.pixelSize: 12 71 | MouseArea { 72 | anchors.fill: parent 73 | onClicked: loader.source = "test06.qml" 74 | } 75 | } 76 | 77 | Text { 78 | text: "Test 07" 79 | font.pixelSize: 12 80 | MouseArea { 81 | anchors.fill: parent 82 | onClicked: loader.source = "test07.qml" 83 | } 84 | } 85 | 86 | Text { 87 | text: "Test 08" 88 | font.pixelSize: 12 89 | MouseArea { 90 | anchors.fill: parent 91 | onClicked: loader.source = "test08.qml" 92 | } 93 | } 94 | 95 | Text { 96 | text: "Test 09" 97 | font.pixelSize: 12 98 | MouseArea { 99 | anchors.fill: parent 100 | onClicked: loader.source = "test09.qml" 101 | } 102 | } 103 | 104 | Text { 105 | text: "Test 10" 106 | font.pixelSize: 12 107 | MouseArea { 108 | anchors.fill: parent 109 | onClicked: loader.source = "test10.qml" 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/test09.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | id: root 9 | width: 640 10 | height: 480 11 | 12 | Timeline { 13 | id: timeline 14 | startFrame: 0 15 | endFrame: 1000 16 | enabled: true 17 | 18 | KeyframeGroup { 19 | target: rectangle2 20 | property: "visible" 21 | 22 | Keyframe { 23 | frame: 0 24 | value: false 25 | } 26 | 27 | Keyframe { 28 | frame: 729 29 | value: true 30 | } 31 | 32 | Keyframe { 33 | frame: 954 34 | value: false 35 | } 36 | } 37 | 38 | KeyframeGroup { 39 | target: rectangle3 40 | property: "visible" 41 | 42 | Keyframe { 43 | frame: 0 44 | value: false 45 | } 46 | 47 | Keyframe { 48 | frame: 793 49 | value: true 50 | } 51 | 52 | Keyframe { 53 | frame: 868 54 | value: false 55 | } 56 | } 57 | 58 | KeyframeGroup { 59 | target: rectangle1 60 | property: "visible" 61 | 62 | Keyframe { 63 | frame: 0 64 | value: false 65 | } 66 | 67 | Keyframe { 68 | frame: 470 69 | value: true 70 | } 71 | 72 | Keyframe { 73 | frame: 757 74 | value: false 75 | } 76 | } 77 | 78 | KeyframeGroup { 79 | target: rectangle 80 | property: "visible" 81 | 82 | Keyframe { 83 | frame: 0 84 | value: false 85 | } 86 | 87 | Keyframe { 88 | frame: 199 89 | value: true 90 | } 91 | 92 | Keyframe { 93 | frame: 546 94 | value: false 95 | } 96 | } 97 | } 98 | 99 | NumberAnimation { 100 | id: numberAnimation 101 | target: timeline 102 | property: "currentFrame" 103 | running: true 104 | loops: -1 105 | to: timeline.endFrame 106 | from: timeline.startFrame 107 | duration: 1000 108 | } 109 | 110 | Rectangle { 111 | id: rectangle 112 | x: 0 113 | y: 0 114 | width: 200 115 | height: 200 116 | color: "#f12929" 117 | visible: false 118 | } 119 | 120 | Rectangle { 121 | id: rectangle1 122 | x: 440 123 | y: 0 124 | width: 200 125 | height: 200 126 | color: "#2851bb" 127 | visible: false 128 | } 129 | 130 | Rectangle { 131 | id: rectangle2 132 | x: 0 133 | y: 280 134 | width: 200 135 | height: 200 136 | color: "#2fd21b" 137 | visible: false 138 | } 139 | 140 | Rectangle { 141 | id: rectangle3 142 | x: 440 143 | y: 280 144 | width: 200 145 | height: 200 146 | color: "#9119dd" 147 | visible: false 148 | } 149 | } 150 | 151 | /*##^## Designer { 152 | D{i:1;currentFrame__AT__NodeInstance:0}D{i:3;timeline_expanded:true}D{i:4;timeline_expanded:true} 153 | D{i:5;timeline_expanded:true}D{i:6;timeline_expanded:true} 154 | } 155 | ##^##*/ 156 | -------------------------------------------------------------------------------- /src/timeline/qquicktimelineanimation.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include "qquicktimelineanimation_p.h" 5 | 6 | #include "qquicktimeline_p.h" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | QT_BEGIN_NAMESPACE 14 | 15 | /*! 16 | \qmltype TimelineAnimation 17 | \inherits NumberAnimation 18 | \nativetype QQuickTimelineAnimation 19 | \inqmlmodule QtQuick.Timeline 20 | \ingroup qtqmltypes 21 | 22 | \brief A number animation attached to a timeline. 23 | 24 | Specifies how the current frame property of a timeline is animated. This 25 | animates the properties of the objects targeted by the timeline. 26 | */ 27 | 28 | /*! 29 | \qmlproperty bool TimelineAnimation::pingPong 30 | 31 | Whether the animation is played backwards after it finishes. This is an easy 32 | way to create circular animations. 33 | */ 34 | 35 | /*! 36 | \qmlsignal TimelineAnimation::finished 37 | 38 | This signal is emitted when the timeline animation finishes. 39 | */ 40 | 41 | QQuickTimelineAnimation::QQuickTimelineAnimation(QObject *parent) : QQuickNumberAnimation(parent) 42 | { 43 | setProperty(QLatin1String("currentFrame")); 44 | connect(this, &QQuickAbstractAnimation::started, this, &QQuickTimelineAnimation::handleStarted); 45 | connect(this, &QQuickAbstractAnimation::stopped, this, &QQuickTimelineAnimation::handleStopped); 46 | } 47 | 48 | void QQuickTimelineAnimation::setPingPong(bool b) 49 | { 50 | if (b == m_pinpong) 51 | return; 52 | 53 | m_pinpong = b; 54 | emit pingPongChanged(); 55 | } 56 | 57 | bool QQuickTimelineAnimation::pingPong() const 58 | { 59 | return m_pinpong; 60 | } 61 | 62 | void QQuickTimelineAnimation::handleStarted() 63 | { 64 | auto timeline = qobject_cast(parent()); 65 | 66 | if (!timeline) 67 | return; 68 | 69 | for (QQuickTimelineAnimation *other : timeline->getAnimations()) { 70 | if (other != this) 71 | other->stop(); 72 | } 73 | 74 | auto *privateObject = static_cast(QObjectPrivate::get(this)); 75 | 76 | if (m_pinpong && m_originalStart) { 77 | m_originalLoop = privateObject->loopCount; 78 | m_currentLoop = 0; 79 | privateObject->loopCount = 1; 80 | privateObject->animationInstance->setLoopCount(1); 81 | m_originalStart = false; 82 | m_reversed = false; 83 | } 84 | } 85 | 86 | static void swapStartEnd(QQuickPropertyAnimationPrivate *privateObject) 87 | { 88 | std::swap(privateObject->to, privateObject->from); 89 | } 90 | 91 | void QQuickTimelineAnimation::handleStopped() 92 | { 93 | if (!m_pinpong) { 94 | emit finished(); 95 | return; 96 | } 97 | 98 | auto *privateObject = static_cast(QObjectPrivate::get(this)); 99 | 100 | if (m_reversed) 101 | m_currentLoop++; 102 | 103 | if (!(privateObject->animationInstance->currentTime() < privateObject->duration) 104 | && (m_currentLoop < m_originalLoop 105 | || m_originalLoop == -1)) { 106 | swapStartEnd(privateObject); 107 | 108 | m_reversed = !m_reversed; 109 | QQuickTimelineAnimation::start(); 110 | 111 | } else { 112 | if (m_reversed) 113 | swapStartEnd(privateObject); 114 | 115 | m_originalStart = true; 116 | m_reversed = false; 117 | privateObject->loopCount = m_originalLoop; 118 | emit finished(); 119 | } 120 | } 121 | 122 | QT_END_NAMESPACE 123 | -------------------------------------------------------------------------------- /src/timeline/qquickkeyframe_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QQUICKKEYFRAME_P_H 5 | #define QQUICKKEYFRAME_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include "qtquicktimelineglobal_p.h" 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | QT_BEGIN_NAMESPACE 26 | 27 | class QQuickKeyframeGroupPrivate; 28 | class QQuickKeyframePrivate; 29 | class QQuickNumberKeyframePrivate; 30 | 31 | class Q_QUICKTIMELINE_EXPORT QQuickKeyframe : public QObject 32 | { 33 | Q_OBJECT 34 | Q_DECLARE_PRIVATE(QQuickKeyframe) 35 | 36 | Q_PROPERTY(qreal frame READ frame WRITE setFrame NOTIFY frameChanged) 37 | Q_PROPERTY(QEasingCurve easing READ easing WRITE setEasing NOTIFY easingCurveChanged) 38 | Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) 39 | 40 | QML_NAMED_ELEMENT(Keyframe) 41 | QML_ADDED_IN_VERSION(1, 0) 42 | 43 | public: 44 | explicit QQuickKeyframe(QObject *parent = nullptr); 45 | 46 | qreal frame() const; 47 | void setFrame(qreal); 48 | void reset(); 49 | 50 | QEasingCurve easing() const; 51 | void setEasing(const QEasingCurve &); 52 | 53 | QVariant value() const; 54 | void setValue(const QVariant &v); 55 | 56 | virtual QVariant evaluate(QQuickKeyframe *pre, qreal frame, int userType) const; 57 | 58 | protected: 59 | QQuickKeyframe(QQuickKeyframePrivate &dd, QObject *parent); 60 | 61 | Q_SIGNALS: 62 | void frameChanged(); 63 | void easingCurveChanged(); 64 | void valueChanged(); 65 | }; 66 | 67 | class Q_QUICKTIMELINE_EXPORT QQuickKeyframeGroup : public QObject, public QQmlParserStatus 68 | { 69 | Q_OBJECT 70 | Q_DECLARE_PRIVATE(QQuickKeyframeGroup) 71 | 72 | Q_INTERFACES(QQmlParserStatus) 73 | 74 | Q_PROPERTY(QObject *target READ target WRITE setTargetObject NOTIFY targetChanged) 75 | Q_PROPERTY(QString property READ property WRITE setProperty NOTIFY propertyChanged) 76 | Q_PROPERTY(QQmlListProperty keyframes READ keyframes) 77 | Q_PROPERTY(QUrl keyframeSource READ keyframeSource WRITE setKeyframeSource NOTIFY keyframeSourceChanged REVISION(1, 1)) 78 | 79 | QML_NAMED_ELEMENT(KeyframeGroup) 80 | QML_ADDED_IN_VERSION(1, 0) 81 | 82 | Q_CLASSINFO("DefaultProperty", "keyframes") 83 | 84 | public: 85 | explicit QQuickKeyframeGroup(QObject *parent = nullptr); 86 | 87 | QQmlListProperty keyframes(); 88 | 89 | QObject *target() const; 90 | void setTargetObject(QObject *); 91 | 92 | QString property() const; 93 | void setProperty(const QString &); 94 | 95 | Q_REVISION(1, 1) QUrl keyframeSource() const; 96 | Q_REVISION(1, 1) void setKeyframeSource(const QUrl &source); 97 | 98 | const QByteArray keyframeData() const; 99 | void setKeyframeData(const QByteArray &data); 100 | 101 | QVariant evaluate(qreal frame) const; 102 | 103 | void setProperty(qreal frame); 104 | 105 | void init(); 106 | 107 | void resetDefaultValue(); 108 | 109 | void reset(); 110 | 111 | protected: 112 | void setupKeyframes(); 113 | 114 | void classBegin() override; 115 | void componentComplete() override; 116 | 117 | Q_SIGNALS: 118 | void targetChanged(); 119 | void propertyChanged(); 120 | Q_REVISION(1, 1) void keyframeSourceChanged(); 121 | }; 122 | 123 | QT_END_NAMESPACE 124 | 125 | #endif // QQUICKKEYFRAME_P_H 126 | -------------------------------------------------------------------------------- /src/timeline/qquickkeyframedatautils_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QQUICKKEYFRAMEDATAUTILS_H 5 | #define QQUICKKEYFRAMEDATAUTILS_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | // This file contains useful methods to write keyframes 31 | // CBOR binary files and understand the format. 32 | 33 | void writeKeyframesHeader(QCborStreamWriter &writer, QMetaType::Type type, int version = 1) 34 | { 35 | // Root array 36 | writer.startArray(); 37 | // header name 38 | writer.append("QTimelineKeyframes"); 39 | // file version 40 | writer.append(version); 41 | // property type 42 | writer.append(type); 43 | } 44 | 45 | // Write QVariant value into CBOR in correct type. 46 | void writeValue(QCborStreamWriter &writer, const QVariant &value) 47 | { 48 | const QMetaType type = value.metaType(); 49 | switch (type.id()) { 50 | case QMetaType::Bool: { 51 | bool b = value.toBool(); 52 | writer.append(b); 53 | break; 54 | } 55 | case QMetaType::Int: { 56 | int i = value.toInt(); 57 | writer.append(i); 58 | break; 59 | } 60 | case QMetaType::Float: { 61 | float f = value.toFloat(); 62 | writer.append(f); 63 | break; 64 | } 65 | case QMetaType::Double: { 66 | double d = value.toDouble(); 67 | writer.append(d); 68 | break; 69 | } 70 | case QMetaType::QVector2D: { 71 | QVector2D v = value.value(); 72 | writer.append(v.x()); 73 | writer.append(v.y()); 74 | break; 75 | } 76 | case QMetaType::QVector3D: { 77 | QVector3D v = value.value(); 78 | writer.append(v.x()); 79 | writer.append(v.y()); 80 | writer.append(v.z()); 81 | break; 82 | } 83 | case QMetaType::QVector4D: { 84 | QVector4D v = value.value(); 85 | writer.append(v.x()); 86 | writer.append(v.y()); 87 | writer.append(v.z()); 88 | writer.append(v.w()); 89 | break; 90 | } 91 | case QMetaType::QQuaternion: { 92 | QQuaternion q = value.value(); 93 | writer.append(q.scalar()); 94 | writer.append(q.x()); 95 | writer.append(q.y()); 96 | writer.append(q.z()); 97 | break; 98 | } 99 | case QMetaType::QColor: { 100 | QColor c = value.value(); 101 | writer.append(c.red()); 102 | writer.append(c.green()); 103 | writer.append(c.blue()); 104 | writer.append(c.alpha()); 105 | break; 106 | } 107 | case QMetaType::QRect: { 108 | QRect r = value.value(); 109 | writer.append(r.x()); 110 | writer.append(r.y()); 111 | writer.append(r.width()); 112 | writer.append(r.height()); 113 | break; 114 | } 115 | default: { 116 | qDebug() << "Not able to add:" << value << "of type:" << type.name(); 117 | qDebug() << "Please add support for this type into generator."; 118 | break; 119 | } 120 | } 121 | } 122 | 123 | #endif // QQUICKKEYFRAMEDATAUTILS_H 124 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/test10.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | // Note: KeyframeGroup keyframeSource requires 1.1 version 6 | import QtQuick.Timeline 1.1 7 | 8 | Item { 9 | id: rootItem 10 | property vector3d v3: Qt.vector3d(0,0,0) 11 | 12 | Rectangle { 13 | id: rectangle1 14 | anchors.verticalCenter: parent.verticalCenter 15 | anchors.left: parent.left 16 | anchors.leftMargin: 10 17 | width: 120 18 | height: 120 19 | color: "red" 20 | radius: width / 2 21 | } 22 | Rectangle { 23 | id: rectangle2 24 | anchors.verticalCenter: parent.verticalCenter 25 | anchors.right: parent.right 26 | anchors.rightMargin: 10 27 | width: 120 28 | height: 120 29 | color: "blue" 30 | radius: width / 2 31 | } 32 | Rectangle { 33 | id: rectangle3 34 | anchors.verticalCenter: parent.verticalCenter 35 | anchors.horizontalCenter: parent.horizontalCenter 36 | width: 120 37 | height: 120 38 | color: "black" 39 | radius: width / 2 40 | } 41 | Text { 42 | font.pixelSize: 20 43 | anchors.horizontalCenter: parent.horizontalCenter 44 | anchors.top: parent.top 45 | anchors.topMargin: 10 46 | text: "value: " + (rootItem.v3.x).toFixed(2) 47 | + ", " + (rootItem.v3.y).toFixed(2) 48 | + ", " + (rootItem.v3.z).toFixed(2) 49 | color: "black" 50 | } 51 | 52 | Timeline { 53 | id: timeline1 54 | enabled: true 55 | startFrame: 0 56 | endFrame: 1000 57 | 58 | animations: [ 59 | TimelineAnimation { 60 | running: true 61 | duration: 2000 62 | from: 0 63 | to: 2000 64 | loops: Animation.Infinite 65 | } 66 | ] 67 | KeyframeGroup { 68 | target: rectangle1 69 | property: "opacity" 70 | keyframeSource: "animate_real.cbor" 71 | } 72 | } 73 | 74 | Timeline { 75 | id: timeline2 76 | enabled: true 77 | startFrame: 0 78 | endFrame: 2000 79 | 80 | animations: [ 81 | TimelineAnimation { 82 | running: true 83 | duration: 2000 84 | from: 0 85 | to: 2000 86 | loops: Animation.Infinite 87 | } 88 | ] 89 | KeyframeGroup { 90 | target: rectangle2 91 | property: "visible" 92 | keyframeSource: "animate_bool.cbor" 93 | } 94 | } 95 | 96 | Timeline { 97 | id: timeline3 98 | enabled: true 99 | startFrame: 0 100 | endFrame: 2000 101 | 102 | animations: [ 103 | TimelineAnimation { 104 | running: true 105 | duration: 2000 106 | from: 0 107 | to: 2000 108 | loops: Animation.Infinite 109 | } 110 | ] 111 | KeyframeGroup { 112 | target: rectangle3 113 | property: "color" 114 | keyframeSource: "animate_color.cbor" 115 | } 116 | } 117 | 118 | Timeline { 119 | id: timeline4 120 | enabled: true 121 | startFrame: 0 122 | endFrame: 2000 123 | 124 | animations: [ 125 | TimelineAnimation { 126 | running: true 127 | duration: 10000 128 | from: 0 129 | to: 2000 130 | loops: Animation.Infinite 131 | } 132 | ] 133 | KeyframeGroup { 134 | target: rootItem 135 | property: "v3" 136 | keyframeSource: "animate_vector3d.cbor" 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/auto/qtquicktimeline_blendtrees/data/BlendTreeTest.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 5 | import QtQuick.Timeline 6 | import QtQuick.Timeline.BlendTrees 7 | 8 | Item { 9 | Item { 10 | width: 480 11 | height: 480 12 | 13 | TimelineAnimation { 14 | objectName: "animation1" 15 | id: animation1 16 | duration: 20000 17 | loops: -1 18 | from: 0 19 | to: 100 20 | } 21 | TimelineAnimation { 22 | objectName: "animation2" 23 | id: animation2 24 | duration: 20000 25 | loops: -1 26 | from: 100 27 | to: 200 28 | } 29 | 30 | Timeline { 31 | id: timeline 32 | objectName: "timeline" 33 | 34 | enabled: true 35 | 36 | KeyframeGroup { 37 | objectName: "group01" 38 | target: rectangle 39 | property: "x" 40 | 41 | Keyframe { 42 | frame: 0 43 | value: 0 44 | } 45 | 46 | Keyframe { 47 | objectName: "keyframe" 48 | frame: 50 49 | value: 100 50 | } 51 | 52 | Keyframe { 53 | frame: 100 54 | value: 200 55 | } 56 | 57 | Keyframe { 58 | frame: 150 59 | value: 100 60 | } 61 | 62 | Keyframe { 63 | frame: 200 64 | value: 0 65 | } 66 | } 67 | 68 | KeyframeGroup { 69 | target: rectangle 70 | property: "y" 71 | 72 | Keyframe { 73 | frame: 0 74 | value: 0 75 | } 76 | 77 | Keyframe { 78 | frame: 50 79 | value: 100 80 | } 81 | 82 | Keyframe { 83 | objectName: "easingBounce" 84 | frame: 100 85 | value: 200 86 | easing.type: Easing.InBounce 87 | } 88 | 89 | Keyframe { 90 | frame: 150 91 | value: 300 92 | } 93 | 94 | Keyframe { 95 | frame: 200 96 | value: 400 97 | } 98 | } 99 | 100 | KeyframeGroup { 101 | target: rectangle 102 | property: "color" 103 | 104 | Keyframe { 105 | frame: 0 106 | value: "red" 107 | } 108 | 109 | Keyframe { 110 | frame: 50 111 | value: "blue" 112 | } 113 | 114 | Keyframe { 115 | frame: 100 116 | value: "yellow" 117 | } 118 | 119 | Keyframe { 120 | frame: 150 121 | value: "cyan" 122 | } 123 | 124 | Keyframe { 125 | frame: 200 126 | value: "magenta" 127 | } 128 | } 129 | } 130 | 131 | TimelineAnimationNode { 132 | id: animation1Node 133 | objectName: "animation1Node" 134 | timeline: timeline 135 | animation: animation1 136 | } 137 | 138 | TimelineAnimationNode { 139 | id: animation2Node 140 | objectName: "animation2Node" 141 | timeline: timeline 142 | animation: animation2 143 | } 144 | 145 | BlendAnimationNode { 146 | id: animationBlendNode 147 | objectName: "blendAnimation" 148 | source1: animation1Node 149 | source2: animation2Node 150 | weight: 0.5 151 | outputEnabled: true 152 | } 153 | 154 | Rectangle { 155 | id: rectangle 156 | 157 | objectName: "rectangle" 158 | 159 | width: 20 160 | height: 20 161 | color: "red" 162 | } 163 | 164 | AnimationController { 165 | id: animation1Controller 166 | objectName: "animation1Controller" 167 | animation: animation1 168 | } 169 | AnimationController { 170 | id: animation2Controller 171 | objectName: "animation2Controller" 172 | animation: animation2 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/timeline/blendtrees/qtimelineanimationnode.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include "qtimelineanimationnode_p.h" 5 | 6 | QT_BEGIN_NAMESPACE 7 | 8 | /*! 9 | \qmltype TimelineAnimationNode 10 | \inherits BlendTreeNode 11 | \nativetype QTimelineAnimationNode 12 | \inqmlmodule QtQuick.Timeline.BlendTrees 13 | \ingroup qtqmltypes 14 | 15 | \brief A blend tree source node that plays a timeline animation. 16 | 17 | TimelineAnimationNode is a blend tree source node that plays a timeline 18 | animation and outputs the animation's frame data. This node wraps a 19 | TimelineAnimation and its associated Timeline and provides a way to 20 | intercept the animation's frame data and output it to the blend tree. 21 | */ 22 | 23 | /*! 24 | \qmlproperty TimelineAnimation TimelineAnimationNode::animation 25 | 26 | This property holds the timeline animation to play. 27 | */ 28 | 29 | /*! 30 | \qmlproperty Timeline TimelineAnimationNode::timeline 31 | 32 | This property holds the timeline that the animation is played on. 33 | */ 34 | 35 | /*! 36 | \qmlproperty real TimelineAnimationNode::currentFrame 37 | 38 | This property holds the current frame of the animation. 39 | */ 40 | 41 | QTimelineAnimationNode::QTimelineAnimationNode(QObject *parent) 42 | : QBlendTreeNode(parent) 43 | { 44 | 45 | } 46 | 47 | QQuickTimelineAnimation *QTimelineAnimationNode::animation() const 48 | { 49 | return m_animation; 50 | } 51 | 52 | void QTimelineAnimationNode::setAnimation(QQuickTimelineAnimation *newAnimation) 53 | { 54 | if (m_animation == newAnimation) 55 | return; 56 | 57 | if (m_animation) 58 | disconnect(m_animationDestroyedConnection); 59 | 60 | m_animation = newAnimation; 61 | 62 | if (m_animation) 63 | m_animationDestroyedConnection = connect(m_animation, 64 | &QObject::destroyed, 65 | this, 66 | [this] {setAnimation(nullptr);}); 67 | 68 | updateAnimationTarget(); 69 | updateFrameData(); 70 | Q_EMIT animationChanged(); 71 | } 72 | 73 | QQuickTimeline *QTimelineAnimationNode::timeline() const 74 | { 75 | return m_timeline; 76 | } 77 | 78 | void QTimelineAnimationNode::setTimeline(QQuickTimeline *newTimeline) 79 | { 80 | if (m_timeline == newTimeline) 81 | return; 82 | 83 | if (m_timeline) 84 | disconnect(m_timelineDestroyedConnection); 85 | 86 | m_timeline = newTimeline; 87 | 88 | if (m_timeline) 89 | m_timelineDestroyedConnection = connect(m_timeline, 90 | &QObject::destroyed, 91 | this, 92 | [this] {setTimeline(nullptr);}); 93 | 94 | updateFrameData(); 95 | Q_EMIT timelineChanged(); 96 | } 97 | 98 | qreal QTimelineAnimationNode::currentFrame() const 99 | { 100 | return m_currentFrame; 101 | } 102 | 103 | void QTimelineAnimationNode::setCurrentFrame(qreal newCurrentFrame) 104 | { 105 | if (qFuzzyCompare(m_currentFrame, newCurrentFrame)) 106 | return; 107 | m_currentFrame = newCurrentFrame; 108 | updateFrameData(); 109 | Q_EMIT currentFrameChanged(); 110 | } 111 | 112 | static QHash getFrameData(QQuickTimeline *timeline, qreal frame) 113 | { 114 | QHash frameData; 115 | if (timeline) { 116 | QQmlListReference keyframeGroups(timeline, "keyframeGroups"); 117 | if (keyframeGroups.isValid() && keyframeGroups.isReadable()) { 118 | for (int i = 0; i < keyframeGroups.count(); ++i) { 119 | QQuickKeyframeGroup *keyframeGroup = qobject_cast(keyframeGroups.at(i)); 120 | if (keyframeGroup && keyframeGroup->target()) { 121 | QQmlProperty qmlProperty(keyframeGroup->target(), keyframeGroup->property()); 122 | QVariant value = keyframeGroup->evaluate(frame); 123 | frameData.insert(qmlProperty, value); 124 | } 125 | } 126 | } 127 | } 128 | 129 | return frameData; 130 | } 131 | 132 | void QTimelineAnimationNode::updateFrameData() 133 | { 134 | if (!m_animation || !m_timeline) 135 | return; 136 | 137 | m_frameData = getFrameData(m_timeline, m_currentFrame); 138 | Q_EMIT frameDataChanged(); 139 | } 140 | 141 | void QTimelineAnimationNode::updateAnimationTarget() 142 | { 143 | if (!m_animation) 144 | return; 145 | // Property should already be set to "currentFrame" 146 | m_animation->setTargetObject(this); 147 | } 148 | 149 | QT_END_NAMESPACE 150 | -------------------------------------------------------------------------------- /tests/manual/keyframeBinaryGenerator/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "keyframedatautils_p.h" 12 | 13 | struct Keyframe 14 | { 15 | double frame; 16 | QVariant value; 17 | int easing = 0; 18 | }; 19 | 20 | struct KeyframeData 21 | { 22 | QString filename; 23 | QMetaType::Type propertyType; 24 | QList keyframes; 25 | }; 26 | 27 | int main(int argc, char *argv[]) 28 | { 29 | QCoreApplication app(argc, argv); 30 | 31 | QList data; 32 | 33 | // Animate real ('opacity' property) 34 | KeyframeData keyframeData1; 35 | keyframeData1.filename = "animate_real.cbor"; 36 | keyframeData1.propertyType = QMetaType::QReal; 37 | Keyframe key1_1; 38 | key1_1.frame = 0; 39 | key1_1.value = 0.0; 40 | key1_1.easing = 0; 41 | keyframeData1.keyframes.append(key1_1); 42 | Keyframe key1_2; 43 | key1_2.frame = 1000; 44 | key1_2.value = 1.0; 45 | key1_2.easing = 0; 46 | keyframeData1.keyframes.append(key1_2); 47 | Keyframe key1_3; 48 | key1_3.frame = 2000; 49 | key1_3.value = 0.0; 50 | key1_3.easing = 0; 51 | keyframeData1.keyframes.append(key1_3); 52 | data.append(keyframeData1); 53 | 54 | // Animate bool ('visible' property) 55 | KeyframeData keyframeData2; 56 | keyframeData2.filename = "animate_bool.cbor"; 57 | keyframeData2.propertyType = QMetaType::Bool; 58 | Keyframe key2_1; 59 | key2_1.frame = 0; 60 | key2_1.value = true; 61 | key2_1.easing = 0; 62 | keyframeData2.keyframes.append(key2_1); 63 | Keyframe key2_2; 64 | key2_2.frame = 1000; 65 | key2_2.value = false; 66 | key2_2.easing = 0; 67 | keyframeData2.keyframes.append(key2_2); 68 | Keyframe key2_3; 69 | key2_3.frame = 2000; 70 | key2_3.value = true; 71 | key2_3.easing = 0; 72 | keyframeData2.keyframes.append(key2_3); 73 | data.append(keyframeData2); 74 | 75 | // Animate color 76 | KeyframeData keyframeData3; 77 | keyframeData3.filename = "animate_color.cbor"; 78 | keyframeData3.propertyType = QMetaType::QColor; 79 | Keyframe key3_1; 80 | key3_1.frame = 0; 81 | key3_1.value = QColor("green"); 82 | key3_1.easing = 0; 83 | keyframeData3.keyframes.append(key3_1); 84 | Keyframe key3_2; 85 | key3_2.frame = 1000; 86 | key3_2.value = QColor(255, 255, 0); 87 | key3_2.easing = 0; 88 | keyframeData3.keyframes.append(key3_2); 89 | Keyframe key3_3; 90 | key3_3.frame = 2000; 91 | key3_3.value = QColor(255, 255, 255); 92 | key3_3.easing = 0; 93 | keyframeData3.keyframes.append(key3_3); 94 | data.append(keyframeData3); 95 | 96 | // Animate vector3d 97 | KeyframeData keyframeData4; 98 | keyframeData4.filename = "animate_vector3d.cbor"; 99 | keyframeData4.propertyType = QMetaType::QVector3D; 100 | Keyframe key4_1; 101 | key4_1.frame = 0; 102 | key4_1.value = QVector3D(500.0, 500.0, 500.0); 103 | key4_1.easing = 0; 104 | keyframeData4.keyframes.append(key4_1); 105 | Keyframe key4_2; 106 | key4_2.frame = 1000; 107 | key4_2.value = QVector3D(100.0, 200.0, 300.0); 108 | key4_2.easing = 0; 109 | keyframeData4.keyframes.append(key4_2); 110 | Keyframe key4_3; 111 | key4_3.frame = 2000; 112 | key4_3.value = QVector3D(-1000.0, 0.0, 0.0); 113 | key4_3.easing = 0; 114 | keyframeData4.keyframes.append(key4_3); 115 | data.append(keyframeData4); 116 | 117 | for (auto keyframeData : data) { 118 | // Output into current running path 119 | QString path = QDir::currentPath(); 120 | QString outputFile = path + "\\" + keyframeData.filename; 121 | QFile output(outputFile); 122 | 123 | qDebug() << "Generating:" << outputFile; 124 | 125 | if (!output.open(QIODevice::WriteOnly)) { 126 | qWarning() << "Unable to open output file:" << outputFile; 127 | exit(0); 128 | } 129 | 130 | QCborStreamWriter writer(&output); 131 | 132 | writeKeyframesHeader(writer, keyframeData.propertyType); 133 | 134 | // Start keyframes array 135 | writer.startArray(); 136 | for (auto keyFrame : keyframeData.keyframes) { 137 | writer.append(keyFrame.frame); 138 | writer.append(keyFrame.easing); 139 | writeValue(writer, keyFrame.value); 140 | } 141 | // End Keyframes array 142 | writer.endArray(); 143 | 144 | // End root array 145 | writer.endArray(); 146 | 147 | output.close(); 148 | } 149 | 150 | return app.exec(); 151 | } 152 | -------------------------------------------------------------------------------- /licenseRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "comment": ["file_pattern_ending: strings matched against the end of a file name.", 4 | "location keys: regular expression matched against the beginning of", 5 | "the file path (relative to the git submodule root).", 6 | "spdx: list of SPDX-License-Expression's allowed in the matching files.", 7 | "-------------------------------------------------------", 8 | "Files with the following endings are Build System licensed,", 9 | "unless they are examples", 10 | "Files with other endings can also be build system files" 11 | ], 12 | "file_pattern_ending": ["CMakeLists.txt", ".cmake", ".pro", ".pri", ".prf", 13 | "configure", "configure.bat", "cmake.in", "plist.in", "CMakeLists.txt.in", 14 | ".cmake.conf", ".tag", ".yaml", "ci_config_linux.json", 15 | "configure.json", ".qrc"], 16 | "location": { 17 | "": { 18 | "comment": "Default", 19 | "file type": "build system", 20 | "spdx": ["BSD-3-Clause"] 21 | }, 22 | "(.*)(examples/|snippets/)": { 23 | "comment": "Example takes precedence", 24 | "file type": "examples and snippets", 25 | "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] 26 | } 27 | } 28 | }, 29 | { 30 | "comments": ["Files with the following endings are infrastructure licensed"], 31 | "file_pattern_ending": [".gitattributes", ".gitignore", ".gitmodules", ".gitreview", 32 | "_clang-format", "licenseRule.json", "REUSE.toml"], 33 | "location":{ 34 | "": { 35 | "comment": "Default", 36 | "file type": "infrastructure", 37 | "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] 38 | } 39 | } 40 | }, 41 | { 42 | "comments": ["Files with the following endings are Tool licensed,", 43 | "unless they are examples.", 44 | "Files with other endings can also be tool files."], 45 | "file_pattern_ending": [".sh", ".py", ".pl", ".bat", ".ps1"], 46 | "location":{ 47 | "": { 48 | "comment": "Default", 49 | "file type": "tools and utils", 50 | "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0"] 51 | }, 52 | "(.*)(examples/|snippets/)": { 53 | "comment": "Example takes precedence", 54 | "file type": "examples and snippets", 55 | "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] 56 | } 57 | } 58 | }, 59 | { 60 | "comment": "Files with the following endings are Documentation licensed.", 61 | "file_pattern_ending": [".qdoc", ".qdocinc" , ".qdocconf", "README", "qt_attribution.json", 62 | ".css"], 63 | "location":{ 64 | "": { 65 | "comment": "", 66 | "file type": "documentation", 67 | "spdx": ["LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only"] 68 | } 69 | } 70 | }, 71 | { 72 | "comment": ["All other files", 73 | "The licensing is defined only by the file location in the Qt module repository.", 74 | "NO key for this case!", 75 | "This needs to be the last entry of the file."], 76 | "location": { 77 | "": { 78 | "comment": "Default", 79 | "file type": "module and plugin", 80 | "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] 81 | }, 82 | "dist/": { 83 | "comment": "Default", 84 | "file type": "documentation", 85 | "spdx": ["LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only"] 86 | }, 87 | "src/": { 88 | "comment": "Default", 89 | "file type": "module and plugin", 90 | "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] 91 | }, 92 | "tests/": { 93 | "comment": "Default", 94 | "file type": "test", 95 | "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] 96 | }, 97 | "(.*)(examples/|snippets/)": { 98 | "comment": "Default", 99 | "file type": "examples and snippets", 100 | "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] 101 | }, 102 | ".*doc/images/": { 103 | "comment": "Default", 104 | "file type": "documentation", 105 | "spdx": ["LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only"] 106 | } 107 | } 108 | } 109 | ] 110 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/test08.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | id: item1 9 | width: 640 10 | height: 480 11 | 12 | Timeline { 13 | id: keyframeMutator 14 | endFrame: 1000 15 | enabled: true 16 | 17 | animations: [ 18 | TimelineAnimation { 19 | running: true 20 | duration: 1000 21 | loops: -1 22 | from: keyframeMutator.startFrame 23 | to: keyframeMutator.endFrame 24 | } 25 | ] 26 | 27 | KeyframeGroup { 28 | target: circle1 29 | property: "width" 30 | Keyframe { 31 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 32 | frame: 500 33 | value: 250 34 | } 35 | 36 | Keyframe { 37 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 38 | frame: 1000 39 | value: 50 40 | } 41 | } 42 | 43 | KeyframeGroup { 44 | target: circle1 45 | property: "height" 46 | Keyframe { 47 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 48 | frame: 500 49 | value: 250 50 | } 51 | 52 | Keyframe { 53 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 54 | frame: 1000 55 | value: 50 56 | } 57 | } 58 | 59 | KeyframeGroup { 60 | target: circle1 61 | property: "opacity" 62 | 63 | Keyframe { 64 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 65 | frame: 500 66 | value: 1 67 | } 68 | 69 | Keyframe { 70 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 71 | frame: 1000 72 | value: 0 73 | } 74 | } 75 | 76 | KeyframeGroup { 77 | target: circle 78 | property: "opacity" 79 | 80 | Keyframe { 81 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 82 | frame: 500 83 | value: 1 84 | } 85 | } 86 | 87 | KeyframeGroup { 88 | target: circle 89 | property: "width" 90 | 91 | Keyframe { 92 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 93 | frame: 500 94 | value: 180 95 | } 96 | 97 | Keyframe { 98 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 99 | frame: 1000 100 | value: 80 101 | } 102 | } 103 | 104 | KeyframeGroup { 105 | target: circle 106 | property: "height" 107 | 108 | Keyframe { 109 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 110 | frame: 500 111 | value: 180 112 | } 113 | 114 | Keyframe { 115 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 116 | frame: 1000 117 | value: 80 118 | } 119 | } 120 | 121 | KeyframeGroup { 122 | target: circle2 123 | property: "opacity" 124 | 125 | Keyframe { 126 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 127 | frame: 1000 128 | value: 0 129 | } 130 | } 131 | 132 | KeyframeGroup { 133 | target: circle2 134 | property: "width" 135 | 136 | Keyframe { 137 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 138 | frame: 1000 139 | value: 10 140 | } 141 | } 142 | 143 | KeyframeGroup { 144 | target: circle2 145 | property: "height" 146 | 147 | Keyframe { 148 | easing.bezierCurve: [0.65,0.05,0.35,1.00,1,1] 149 | frame: 1000 150 | value: 10 151 | } 152 | } 153 | } 154 | 155 | 156 | Circle { 157 | id: circle 158 | x: 283 159 | y: 202 160 | width: 75 161 | height: 76 162 | color: "#00ffffff" 163 | opacity: 0 164 | anchors.horizontalCenter: parent.horizontalCenter 165 | anchors.verticalCenter: parent.verticalCenter 166 | borderWidth: 4 167 | borderColor: "#808080" 168 | } 169 | 170 | Circle { 171 | id: circle1 172 | x: 280 173 | y: 206 174 | width: 75 175 | height: 76 176 | color: "#00ffffff" 177 | opacity: 0 178 | borderWidth: 4 179 | borderColor: "#808080" 180 | anchors.horizontalCenter: parent.horizontalCenter 181 | anchors.verticalCenter: parent.verticalCenter 182 | } 183 | 184 | Circle { 185 | id: circle2 186 | x: 272 187 | y: 206 188 | width: 75 189 | height: 76 190 | color: "#00ffffff" 191 | borderWidth: 4 192 | borderColor: "#808080" 193 | anchors.horizontalCenter: parent.horizontalCenter 194 | anchors.verticalCenter: parent.verticalCenter 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/test04.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | id: item1 9 | state: "pingpong" 10 | 11 | Timeline { 12 | id: timeline 13 | enabled: true 14 | startFrame: 0 15 | endFrame: 1000 16 | animations: [ 17 | TimelineAnimation { 18 | id: pingPongAnimation 19 | to: 200 20 | loops: 2 21 | from: 0 22 | duration: 2000 23 | running: false 24 | pingPong: true 25 | onFinished: item1.state = "firsthalf" 26 | }, 27 | 28 | TimelineAnimation { 29 | id: animation01 30 | to: 200 31 | loops: 1 32 | from: 0 33 | duration: 1000 34 | running: false 35 | onFinished: item1.state = "secondhalf (ping pong)" 36 | }, 37 | TimelineAnimation { 38 | id: animation02 39 | to: 400 40 | loops: 1 41 | from: 200 42 | duration: 1000 43 | running: false 44 | pingPong: true 45 | onFinished: item1.state = "last" 46 | }, 47 | TimelineAnimation { 48 | id: animation03 49 | to: 0 50 | loops: 1 51 | from: 200 52 | duration: 500 53 | running: false 54 | onFinished: item1.state = "pingpong" 55 | } 56 | ] 57 | 58 | KeyframeGroup { 59 | target: rectangle 60 | property: "width" 61 | Keyframe { 62 | frame: 1 63 | value: 50 64 | } 65 | } 66 | 67 | KeyframeGroup { 68 | target: rectangle 69 | property: "height" 70 | Keyframe { 71 | frame: 1 72 | value: 50 73 | } 74 | } 75 | 76 | KeyframeGroup { 77 | target: rectangle 78 | property: "x" 79 | Keyframe { 80 | frame: 100 81 | value: 100 82 | } 83 | 84 | Keyframe { 85 | frame: 200 86 | value: 200 87 | } 88 | 89 | Keyframe { 90 | frame: 300 91 | value: 100 92 | } 93 | 94 | Keyframe { 95 | frame: 400 96 | value: 0 97 | } 98 | } 99 | 100 | KeyframeGroup { 101 | target: rectangle 102 | property: "y" 103 | Keyframe { 104 | frame: 100 105 | value: 400 106 | } 107 | 108 | Keyframe { 109 | frame: 200 110 | value: 430 111 | } 112 | 113 | Keyframe { 114 | frame: 300 115 | value: 335 116 | } 117 | 118 | Keyframe { 119 | frame: 400 120 | value: 430 121 | } 122 | } 123 | 124 | KeyframeGroup { 125 | target: rectangle 126 | property: "color" 127 | 128 | Keyframe { 129 | frame: 400 130 | value: "#f61b1b" 131 | } 132 | } 133 | } 134 | 135 | Rectangle { 136 | id: rectangle 137 | x: 0 138 | y: 430 139 | width: 50 140 | height: 50 141 | color: "#f61b1b" 142 | MouseArea { 143 | anchors.topMargin: 109 144 | anchors.fill: parent 145 | onClicked: { 146 | print("clicked") 147 | numberAnimation.start() 148 | } 149 | } 150 | } 151 | 152 | Rectangle { 153 | id: rectangle1 154 | x: 0 155 | y: 0 156 | width: 85 157 | height: 85 158 | color: "#22f4dd" 159 | MouseArea { 160 | anchors.fill: parent 161 | onClicked: { 162 | print("clicked") 163 | numberAnimation.stop() 164 | } 165 | } 166 | } 167 | 168 | Text { 169 | id: text1 170 | x: 376 171 | y: 18 172 | text: item1.state 173 | font.pixelSize: 12 174 | } 175 | states: [ 176 | State { 177 | name: "pingpong" 178 | 179 | PropertyChanges { 180 | target: pingPongAnimation 181 | running: true 182 | } 183 | }, 184 | State { 185 | name: "firsthalf" 186 | PropertyChanges { 187 | target: animation01 188 | running: true 189 | } 190 | }, 191 | State { 192 | name: "secondhalf (ping pong)" 193 | PropertyChanges { 194 | target: animation02 195 | running: true 196 | } 197 | }, 198 | State { 199 | name: "last" 200 | PropertyChanges { 201 | target: animation03 202 | running: true 203 | } 204 | } 205 | ] 206 | 207 | } 208 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/test06.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | width: 640 9 | height: 480 10 | 11 | Timeline { 12 | id: keyframeMutator 13 | enabled: true 14 | endFrame: 1000 15 | 16 | KeyframeGroup { 17 | target: rectangle 18 | property: "x" 19 | Keyframe { 20 | frame: 0 21 | value: 447 22 | } 23 | 24 | Keyframe { 25 | easing.bezierCurve: [0.42,0.00,0.58,1.00,1,1] 26 | frame: 500 27 | value: 220 28 | } 29 | 30 | Keyframe { 31 | easing.bezierCurve: [0.42,0.00,0.58,1.00,1,1] 32 | frame: 1000 33 | value: -9 34 | } 35 | } 36 | 37 | KeyframeGroup { 38 | target: rectangle 39 | property: "y" 40 | Keyframe { 41 | frame: 0 42 | value: 140 43 | } 44 | 45 | Keyframe { 46 | easing.bezierCurve: [0.42,0.00,0.58,1.00,1,1] 47 | frame: 500 48 | value: 140 49 | } 50 | 51 | Keyframe { 52 | frame: 1000 53 | value: 140 54 | } 55 | } 56 | 57 | KeyframeGroup { 58 | target: rectangle1 59 | property: "x" 60 | Keyframe { 61 | frame: 0 62 | value: 220 63 | } 64 | 65 | Keyframe { 66 | easing.bezierCurve: [0.42,0.00,0.58,1.00,1,1] 67 | frame: 500 68 | value: -7 69 | } 70 | 71 | Keyframe { 72 | easing.bezierCurve: [0.42,0.00,0.58,1.00,1,1] 73 | frame: 1000 74 | value: -236 75 | } 76 | } 77 | 78 | KeyframeGroup { 79 | target: rectangle1 80 | property: "y" 81 | Keyframe { 82 | frame: 0 83 | value: 140 84 | } 85 | 86 | Keyframe { 87 | easing.bezierCurve: [0.42,0.00,0.58,1.00,1,1] 88 | frame: 500 89 | value: 140 90 | } 91 | 92 | Keyframe { 93 | easing.bezierCurve: [0.42,0.00,0.58,1.00,1,1] 94 | frame: 1000 95 | value: 140 96 | } 97 | } 98 | 99 | KeyframeGroup { 100 | target: rectangle2 101 | property: "x" 102 | Keyframe { 103 | frame: 0 104 | value: 676 105 | } 106 | 107 | Keyframe { 108 | easing.bezierCurve: [0.42,0.00,0.58,1.00,1,1] 109 | frame: 500 110 | value: 449 111 | } 112 | 113 | Keyframe { 114 | easing.bezierCurve: [0.42,0.00,0.58,1.00,1,1] 115 | frame: 1000 116 | value: 220 117 | } 118 | } 119 | 120 | KeyframeGroup { 121 | target: rectangle2 122 | property: "y" 123 | Keyframe { 124 | frame: 0 125 | value: 140 126 | } 127 | 128 | Keyframe { 129 | easing.bezierCurve: [0.42,0.00,0.58,1.00,1,1] 130 | frame: 500 131 | value: 140 132 | } 133 | 134 | Keyframe { 135 | easing.bezierCurve: [0.42,0.00,0.58,1.00,1,1] 136 | frame: 1000 137 | value: 140 138 | } 139 | } 140 | 141 | KeyframeGroup { 142 | target: rectangle1 143 | property: "scale" 144 | 145 | Keyframe { 146 | frame: 0 147 | value: 1 148 | } 149 | 150 | Keyframe { 151 | frame: 500 152 | value: 0.5 153 | } 154 | } 155 | 156 | KeyframeGroup { 157 | target: rectangle 158 | property: "scale" 159 | 160 | Keyframe { 161 | frame: 0 162 | value: 0.5 163 | } 164 | 165 | Keyframe { 166 | frame: 500 167 | value: 1 168 | } 169 | 170 | Keyframe { 171 | frame: 1000 172 | value: 0.5 173 | } 174 | } 175 | 176 | KeyframeGroup { 177 | target: rectangle2 178 | property: "scale" 179 | 180 | Keyframe { 181 | frame: 500 182 | value: 0.5 183 | } 184 | 185 | Keyframe { 186 | frame: 1000 187 | value: 1 188 | } 189 | } 190 | } 191 | 192 | Rectangle { 193 | id: rectangle 194 | x: 220 195 | y: 140 196 | width: 200 197 | height: 200 198 | color: "#868686" 199 | } 200 | 201 | Rectangle { 202 | id: rectangle1 203 | x: -7 204 | y: 140 205 | width: 200 206 | height: 200 207 | color: "#747474" 208 | } 209 | 210 | Rectangle { 211 | id: rectangle2 212 | x: 449 213 | y: 140 214 | width: 200 215 | height: 200 216 | color: "#767676" 217 | } 218 | 219 | PropertyAnimation { 220 | id: propertyAnimation 221 | target: keyframeMutator 222 | property: "currentFrame" 223 | running: true 224 | to: keyframeMutator.endFrame 225 | duration: 1000 226 | loops: -1 227 | from: keyframeMutator.startFrame 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/test02.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | 9 | Rectangle { 10 | id: leftGauge 11 | x: 20 12 | y: 140 13 | width: 200 14 | height: 200 15 | color: "#969696" 16 | radius: width / 2 17 | 18 | Rectangle { 19 | x: 5 20 | y: 5 21 | width: 190 22 | height: 190 23 | color: "#0d0d0d" 24 | radius: width /2 25 | } 26 | } 27 | 28 | Rectangle { 29 | id: rightGauge 30 | x: 420 31 | y: 140 32 | width: 200 33 | height: 200 34 | color: "#969696" 35 | radius: width / 2 36 | Rectangle { 37 | x: 5 38 | y: 5 39 | width: 190 40 | height: 190 41 | color: "#0d0d0d" 42 | radius: width /2 43 | } 44 | } 45 | 46 | Rectangle { 47 | id: bottomPane 48 | x: 81 49 | y: 424 50 | width: 478 51 | height: 48 52 | color: "#242424" 53 | } 54 | 55 | Timeline { 56 | id: timeline 57 | enabled: true 58 | 59 | startFrame: 0 60 | endFrame: 1000 61 | 62 | animations: [ 63 | 64 | TimelineAnimation { 65 | running: true 66 | duration: 1000 67 | from: 0 68 | to: 1000 69 | } 70 | ] 71 | 72 | KeyframeGroup { 73 | target: leftGauge 74 | property: "x" 75 | Keyframe { 76 | frame: 0 77 | value: -200 78 | } 79 | Keyframe { 80 | frame: 500 81 | value: 0 82 | easing.type: Easing.InQuad 83 | } 84 | 85 | Keyframe { 86 | frame: 1000 87 | value: 20 88 | easing.type: Easing.OutQuad 89 | } 90 | } 91 | KeyframeGroup { 92 | target: leftGauge 93 | property: "y" 94 | Keyframe { 95 | frame: 0 96 | value: 280 97 | } 98 | 99 | Keyframe { 100 | frame: 500 101 | value: 226 102 | easing.type: Easing.InQuad 103 | } 104 | Keyframe { 105 | frame: 1000 106 | value: 140 107 | easing.type: Easing.OutQuad 108 | } 109 | } 110 | KeyframeGroup { 111 | target: leftGauge 112 | property: "opacity" 113 | Keyframe { 114 | frame: 0 115 | value: 0 116 | } 117 | Keyframe { 118 | frame: 500 119 | value: 0.2 120 | easing.type: Easing.InQuad 121 | } 122 | Keyframe { 123 | frame: 1000 124 | value: 1 125 | easing.type: Easing.OutQuad 126 | } 127 | } 128 | 129 | KeyframeGroup { 130 | target: rightGauge 131 | property: "x" 132 | Keyframe { 133 | frame: 0 134 | value: 639 135 | } 136 | Keyframe { 137 | frame: 500 138 | value: 440 139 | easing.type: Easing.InQuad 140 | } 141 | Keyframe { 142 | frame: 1000 143 | value: 420 144 | easing.type: Easing.OutQuad 145 | } 146 | } 147 | KeyframeGroup { 148 | target: rightGauge 149 | property: "y" 150 | Keyframe { 151 | frame: 0 152 | value: 280 153 | } 154 | Keyframe { 155 | frame: 500 156 | value: 226 157 | easing.type: Easing.InQuad 158 | } 159 | Keyframe { 160 | frame: 1000 161 | value: 140 162 | easing.type: Easing.OutQuad 163 | } 164 | } 165 | KeyframeGroup { 166 | target: rightGauge 167 | property: "opacity" 168 | Keyframe { 169 | frame: 0 170 | value: 0.0 171 | } 172 | Keyframe { 173 | frame: 500 174 | value: 0.2 175 | easing.type: Easing.InQuad 176 | } 177 | Keyframe { 178 | frame: 1000 179 | value: 1.0 180 | easing.type: Easing.OutQuad 181 | } 182 | } 183 | 184 | KeyframeGroup { 185 | target: bottomPane 186 | property: "y" 187 | 188 | Keyframe { 189 | frame: 0 190 | value: 502 191 | } 192 | Keyframe { 193 | frame: 500 194 | value: 432 195 | easing.type: Easing.InQuad 196 | } 197 | 198 | Keyframe { 199 | frame: 1000 200 | value: 424 201 | easing.type: Easing.OutQuad 202 | } 203 | } 204 | KeyframeGroup { 205 | target: bottomPane 206 | property: "opacity" 207 | Keyframe { 208 | frame: 0 209 | value: 0 210 | } 211 | Keyframe { 212 | frame: 500 213 | value: 0.7 214 | easing.type: Easing.InQuad 215 | } 216 | Keyframe { 217 | frame: 1000 218 | value: 1 219 | easing.type: Easing.OutQuad 220 | } 221 | } 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /tests/manual/timelineTestApp/test05.qml: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | import QtQuick 2.0 5 | import QtQuick.Timeline 1.0 6 | 7 | Item { 8 | width: 640 9 | height: 480 10 | 11 | id: root 12 | 13 | state: "onPage02" 14 | 15 | Rectangle { 16 | id: rectangle 17 | x: 0 18 | y: 0 19 | width: 64 20 | height: 64 21 | color: "#747474" 22 | 23 | MouseArea { 24 | anchors.fill: parent 25 | id: leftArea 26 | } 27 | } 28 | 29 | Rectangle { 30 | id: rectangle1 31 | x: 576 32 | y: 0 33 | width: 64 34 | height: 64 35 | color: "#747474" 36 | 37 | MouseArea { 38 | anchors.fill: parent 39 | id: rightArea 40 | } 41 | } 42 | 43 | Item { 44 | id: item1 45 | x: -640 46 | y: 123 47 | width: 1920 48 | height: 480 49 | 50 | Rectangle { 51 | id: page01 52 | x: 0 53 | y: 0 54 | width: 640 55 | height: 359 56 | color: "#ffffff" 57 | 58 | Text { 59 | x: 0 60 | y: 0 61 | text: qsTr("Page 01") 62 | anchors.horizontalCenter: parent.horizontalCenter 63 | anchors.verticalCenter: parent.verticalCenter 64 | font.pixelSize: 38 65 | } 66 | } 67 | 68 | Rectangle { 69 | id: page02 70 | x: 640 71 | y: 0 72 | width: 640 73 | height: 359 74 | color: "#ffffff" 75 | 76 | Text { 77 | x: 0 78 | y: 0 79 | text: qsTr("Page 02") 80 | anchors.verticalCenter: parent.verticalCenter 81 | anchors.horizontalCenter: parent.horizontalCenter 82 | font.pixelSize: 38 83 | } 84 | } 85 | 86 | Rectangle { 87 | id: page03 88 | x: 1280 89 | y: 0 90 | width: 640 91 | height: 359 92 | color: "#ffffff" 93 | 94 | Text { 95 | x: 0 96 | y: 0 97 | text: qsTr("Page 03") 98 | anchors.verticalCenter: parent.verticalCenter 99 | anchors.horizontalCenter: parent.horizontalCenter 100 | font.pixelSize: 38 101 | } 102 | } 103 | } 104 | 105 | Timeline { 106 | id: timeline 107 | endFrame: 1000 108 | startFrame: 0 109 | enabled: true 110 | 111 | animations: [ 112 | TimelineAnimation { 113 | id: animationToPage02FromLeft 114 | from: 0 115 | to: 500 116 | running: false 117 | onFinished: root.state = "onPage02" 118 | }, 119 | TimelineAnimation { 120 | id: animationToPage03FromLeft 121 | from: 500 122 | to: 1000 123 | running: false 124 | onFinished: root.state = "onPage03" 125 | }, 126 | TimelineAnimation { 127 | id: animationToPage02FromRight 128 | from: 1000 129 | to: 500 130 | running: false 131 | onFinished: root.state = "onPage02" 132 | }, 133 | TimelineAnimation { 134 | id: animationToPage01FromRight 135 | from: 500 136 | to: 0 137 | running: false 138 | onFinished: root.state = "onPage01" 139 | } 140 | ] 141 | 142 | KeyframeGroup { 143 | target: item1 144 | property: "x" 145 | 146 | Keyframe { 147 | frame: 0 148 | value: 0 149 | } 150 | 151 | Keyframe { 152 | frame: 500 153 | value: -640 154 | } 155 | 156 | Keyframe { 157 | frame: 1000 158 | value: -1280 159 | } 160 | } 161 | } 162 | 163 | Connections { 164 | target: rightArea 165 | enabled: root.state == "onPage01" 166 | onClicked: root.state = "toPage02FromLeft" 167 | } 168 | 169 | Connections { 170 | target: rightArea 171 | enabled: root.state == "onPage02" 172 | onClicked: root.state = "toPage03FromLeft" 173 | } 174 | 175 | Connections { 176 | target: leftArea 177 | enabled: root.state == "onPage02" 178 | onClicked: root.state = "toPage01FromRight" 179 | } 180 | 181 | Connections { 182 | target: leftArea 183 | enabled: root.state == "onPage03" 184 | onClicked: root.state = "toPage02FromRight" 185 | } 186 | 187 | states: [ 188 | State { 189 | name: "onPage01" 190 | PropertyChanges { 191 | target: timeline 192 | currentFrame: 0 193 | } 194 | }, 195 | State { 196 | name: "onPage02" 197 | PropertyChanges { 198 | target: timeline 199 | currentFrame: 500 200 | } 201 | }, 202 | State { 203 | name: "onPage03" 204 | PropertyChanges { 205 | target: timeline 206 | currentFrame: 1000 207 | } 208 | }, 209 | State { 210 | name: "toPage01FromRight" 211 | PropertyChanges { 212 | target: animationToPage01FromRight 213 | running: true 214 | } 215 | }, 216 | State { 217 | name: "toPage02FromLeft" 218 | PropertyChanges { 219 | target: animationToPage02FromLeft 220 | running: true 221 | } 222 | }, 223 | State { 224 | name: "toPage03FromLeft" 225 | PropertyChanges { 226 | target: animationToPage03FromLeft 227 | running: true 228 | } 229 | }, 230 | State { 231 | name: "toPage02FromRight" 232 | PropertyChanges { 233 | target: animationToPage02FromRight 234 | running: true 235 | } 236 | } 237 | ] 238 | } 239 | 240 | /*##^## Designer { 241 | D{i:62;anchors_width:100;anchors_height:100}D{i:47;currentFrame__AT__NodeInstance:1} 242 | } 243 | ##^##*/ 244 | -------------------------------------------------------------------------------- /src/timeline/qquicktimeline.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include "qquicktimeline_p.h" 5 | 6 | #include 7 | #include 8 | 9 | QT_BEGIN_NAMESPACE 10 | 11 | class QQuickTimelinePrivate : public QObjectPrivate 12 | { 13 | Q_DECLARE_PUBLIC(QQuickTimeline) 14 | public: 15 | QQuickTimelinePrivate() : enabled(false), componentComplete(false) 16 | { 17 | } 18 | 19 | qreal startFrame = 0; 20 | qreal endFrame = 0; 21 | qreal currentFrame = 0; 22 | 23 | bool enabled:1; 24 | bool componentComplete:1; 25 | 26 | protected: 27 | void init(); 28 | void disable(); 29 | 30 | static void append_keyframe(QQmlListProperty *list, QQuickKeyframeGroup *a); 31 | static qsizetype keyframe_count(QQmlListProperty *list); 32 | static QQuickKeyframeGroup* keyframe_at(QQmlListProperty *list, qsizetype pos); 33 | static void clear_keyframes(QQmlListProperty *list); 34 | 35 | static void append_animation(QQmlListProperty *list, QQuickTimelineAnimation *a); 36 | static qsizetype animation_count(QQmlListProperty *list); 37 | static QQuickTimelineAnimation* animation_at(QQmlListProperty *list, qsizetype pos); 38 | static void clear_animations(QQmlListProperty *list); 39 | 40 | QList keyframeGroups; 41 | QList animations; 42 | }; 43 | 44 | void QQuickTimelinePrivate::init() 45 | { 46 | for (auto keyFrames : keyframeGroups) { 47 | keyFrames->init(); 48 | keyFrames->setProperty(currentFrame); 49 | } 50 | } 51 | 52 | void QQuickTimelinePrivate::disable() 53 | { 54 | for (auto keyFrames : keyframeGroups) 55 | keyFrames->resetDefaultValue(); 56 | } 57 | 58 | void QQuickTimelinePrivate::append_keyframe(QQmlListProperty *list, QQuickKeyframeGroup *a) 59 | { 60 | auto q = static_cast(list->object); 61 | q->d_func()->keyframeGroups.append(a); 62 | } 63 | 64 | qsizetype QQuickTimelinePrivate::keyframe_count(QQmlListProperty *list) 65 | { 66 | auto q = static_cast(list->object); 67 | return q->d_func()->keyframeGroups.size(); 68 | } 69 | 70 | QQuickKeyframeGroup* QQuickTimelinePrivate::keyframe_at(QQmlListProperty *list, qsizetype pos) 71 | { 72 | auto q = static_cast(list->object); 73 | return q->d_func()->keyframeGroups.at(pos); 74 | } 75 | 76 | void QQuickTimelinePrivate::clear_keyframes(QQmlListProperty *list) 77 | { 78 | auto q = static_cast(list->object); 79 | while (q->d_func()->keyframeGroups.size()) { 80 | QQuickKeyframeGroup *firstKeyframe = q->d_func()->keyframeGroups.at(0); 81 | q->d_func()->keyframeGroups.removeAll(firstKeyframe); 82 | } 83 | } 84 | 85 | void QQuickTimelinePrivate::append_animation(QQmlListProperty *list, QQuickTimelineAnimation *a) 86 | { 87 | auto q = static_cast(list->object); 88 | a->setTargetObject(q); 89 | q->d_func()->animations.append(a); 90 | } 91 | 92 | qsizetype QQuickTimelinePrivate::animation_count(QQmlListProperty *list) 93 | { 94 | auto q = static_cast(list->object); 95 | return q->d_func()->animations.size(); 96 | } 97 | 98 | QQuickTimelineAnimation* QQuickTimelinePrivate::animation_at(QQmlListProperty *list, qsizetype pos) 99 | { 100 | auto q = static_cast(list->object); 101 | return q->d_func()->animations.at(pos); 102 | } 103 | 104 | void QQuickTimelinePrivate::clear_animations(QQmlListProperty *list) 105 | { 106 | auto q = static_cast(list->object); 107 | while (q->d_func()->animations.size()) { 108 | QQuickTimelineAnimation *firstAnimation = q->d_func()->animations.at(0); 109 | q->d_func()->animations.removeAll(firstAnimation); 110 | } 111 | } 112 | 113 | /*! 114 | \qmltype Timeline 115 | \inherits QtObject 116 | \nativetype QQuickTimeline 117 | \inqmlmodule QtQuick.Timeline 118 | \ingroup qtqmltypes 119 | 120 | \brief A timeline. 121 | 122 | Specifies a timeline with a range of keyframes that contain values for the 123 | properties of an object. The timeline allows specifying the values of items 124 | depending on keyframes and their easing curves. 125 | 126 | A timeline can be either used for animations or to control the behavior of 127 | items. 128 | 129 | For example, it is possible to create a progress bar where the current frame 130 | reflects the progress. 131 | */ 132 | 133 | /*! 134 | \qmlproperty double Timeline::startFrame 135 | 136 | The start of the timeline. 137 | */ 138 | 139 | /*! 140 | \qmlproperty double Timeline::endFrame 141 | 142 | The end of the timeline. 143 | */ 144 | 145 | /*! 146 | \qmlproperty double Timeline::currentFrame 147 | 148 | The current keyframe on the timeline. The current keyframe can be animated 149 | or a binding can be attached to it. Using bindings allows controlling 150 | the behavior of components. 151 | */ 152 | 153 | /*! 154 | \qmlproperty list Timeline::keyframes 155 | \readonly 156 | 157 | This property contains the keyframe groups attached to the timeline. 158 | Each keyframe group contains a list of keyframes for a specific item 159 | and property. 160 | */ 161 | 162 | /*! 163 | \qmlproperty list Timeline::animations 164 | \readonly 165 | 166 | A list of animations attached to the timeline. 167 | */ 168 | 169 | /*! 170 | \qmlproperty bool Timeline::enabled 171 | 172 | Whether the timeline is enabled. 173 | 174 | When the timeline is disabled, all items will have their regular values. 175 | When the timeline is enabled, the values of items are determined by the 176 | current frame and the keyframes. 177 | 178 | Only one timeline should be active at a particular time. 179 | */ 180 | 181 | QQuickTimeline::QQuickTimeline(QObject *parent) : QObject(*(new QQuickTimelinePrivate), parent) 182 | { 183 | } 184 | 185 | QQmlListProperty QQuickTimeline::keyframeGroups() 186 | { 187 | Q_D(QQuickTimeline); 188 | 189 | return { this, &d->keyframeGroups, QQuickTimelinePrivate::append_keyframe, 190 | QQuickTimelinePrivate::keyframe_count, 191 | QQuickTimelinePrivate::keyframe_at, 192 | QQuickTimelinePrivate::clear_keyframes }; 193 | } 194 | 195 | QQmlListProperty QQuickTimeline::animations() 196 | { 197 | Q_D(QQuickTimeline); 198 | 199 | return { this, &d->animations, QQuickTimelinePrivate::append_animation, 200 | QQuickTimelinePrivate::animation_count, 201 | QQuickTimelinePrivate::animation_at, 202 | QQuickTimelinePrivate::clear_animations }; 203 | } 204 | 205 | bool QQuickTimeline::enabled() const 206 | { 207 | Q_D(const QQuickTimeline); 208 | return d->enabled; 209 | } 210 | 211 | void QQuickTimeline::setEnabled(bool b) 212 | { 213 | Q_D(QQuickTimeline); 214 | if (d->enabled == b) 215 | return; 216 | d->enabled = b; 217 | 218 | if (d->componentComplete) { 219 | if (b) 220 | init(); 221 | else 222 | reset(); 223 | } 224 | 225 | emit enabledChanged(); 226 | } 227 | 228 | qreal QQuickTimeline::startFrame() const 229 | { 230 | Q_D(const QQuickTimeline); 231 | return d->startFrame; 232 | } 233 | 234 | void QQuickTimeline::setStartFrame(qreal frame) 235 | { 236 | Q_D(QQuickTimeline); 237 | if (d->startFrame == frame) 238 | return; 239 | d->startFrame = frame; 240 | emit startFrameChanged(); 241 | } 242 | 243 | qreal QQuickTimeline::endFrame() const 244 | { 245 | Q_D(const QQuickTimeline); 246 | return d->endFrame; 247 | } 248 | 249 | void QQuickTimeline::setEndFrame(qreal frame) 250 | { 251 | Q_D(QQuickTimeline); 252 | if (d->endFrame == frame) 253 | return; 254 | d->endFrame = frame; 255 | emit endFrameChanged(); 256 | } 257 | 258 | qreal QQuickTimeline::currentFrame() const 259 | { 260 | Q_D(const QQuickTimeline); 261 | return d->currentFrame; 262 | } 263 | 264 | void QQuickTimeline::setCurrentFrame(qreal frame) 265 | { 266 | Q_D(QQuickTimeline); 267 | if (d->currentFrame == frame) 268 | return; 269 | d->currentFrame = frame; 270 | 271 | reevaluate(); 272 | 273 | emit currentFrameChanged(); 274 | } 275 | 276 | void QQuickTimeline::reevaluate() 277 | { 278 | Q_D(QQuickTimeline); 279 | 280 | if (d->componentComplete && d->enabled) 281 | for (auto keyFrames : d->keyframeGroups) 282 | keyFrames->setProperty(d->currentFrame); 283 | } 284 | 285 | void QQuickTimeline::init() 286 | { 287 | Q_D(QQuickTimeline); 288 | 289 | if (d->componentComplete) 290 | d->init(); 291 | } 292 | 293 | void QQuickTimeline::reset() 294 | { 295 | Q_D(QQuickTimeline); 296 | 297 | if (d->componentComplete) 298 | d->disable(); 299 | } 300 | 301 | QList QQuickTimeline::getAnimations() const 302 | { 303 | Q_D(const QQuickTimeline); 304 | 305 | return d->animations; 306 | } 307 | 308 | void QQuickTimeline::classBegin() 309 | { 310 | Q_D(QQuickTimeline); 311 | d->componentComplete = false; 312 | } 313 | 314 | void QQuickTimeline::componentComplete() 315 | { 316 | Q_D(QQuickTimeline); 317 | d->componentComplete = true; 318 | 319 | if (d->enabled) 320 | init(); 321 | } 322 | 323 | QT_END_NAMESPACE 324 | -------------------------------------------------------------------------------- /src/timeline/blendtrees/qblendanimationnode.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include "qblendanimationnode_p.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | QT_BEGIN_NAMESPACE 16 | 17 | /*! 18 | \qmltype BlendAnimationNode 19 | \inherits BlendTreeNode 20 | \nativetype QBlendAnimationNode 21 | \inqmlmodule QtQuick.Timeline.BlendTrees 22 | \ingroup qtqmltypes 23 | 24 | \brief A blend tree node that blends between two animation sources. 25 | 26 | BlendAnimationNode is a blend tree node that blends between two animation 27 | sources based on a weight value. The weight value can be animated to 28 | dynamically blend between the two animation sources. 29 | */ 30 | 31 | /*! 32 | \qmlproperty BlendTreeNode BlendAnimationNode::source1 33 | 34 | This property holds the first animation source. 35 | */ 36 | 37 | /*! 38 | \qmlproperty BlendTreeNode BlendAnimationNode::source2 39 | 40 | This property holds the second animation source. 41 | */ 42 | 43 | /*! 44 | \qmlproperty real BlendAnimationNode::weight 45 | 46 | This property holds the weight value used to blend between the two animation 47 | sources. 48 | The weight value determines how much of the first animation source is blended 49 | with the second animation source. A weight value of \c 0.0 means the first 50 | animation source is used exclusively, a weight value of \c 1.0 means the 51 | second animation source is used exclusively, and a weight value of \c 0.5 means 52 | both animation sources are blended equally. The default value is \c 0.5. 53 | */ 54 | 55 | QBlendAnimationNode::QBlendAnimationNode(QObject *parent) 56 | : QBlendTreeNode(parent) 57 | { 58 | connect(this, 59 | &QBlendAnimationNode::weightChanged, 60 | this, 61 | &QBlendAnimationNode::handleInputFrameDataChanged); 62 | } 63 | 64 | QBlendTreeNode *QBlendAnimationNode::source1() const 65 | { 66 | return m_source1; 67 | } 68 | 69 | void QBlendAnimationNode::setSource1(QBlendTreeNode *newSource1) 70 | { 71 | if (m_source1 == newSource1) 72 | return; 73 | 74 | if (m_source1) { 75 | disconnect(m_source1OutputConnection); 76 | disconnect(m_source1DestroyedConnection); 77 | } 78 | 79 | m_source1 = newSource1; 80 | 81 | if (m_source1) { 82 | m_source1OutputConnection = connect(m_source1, 83 | &QBlendTreeNode::frameDataChanged, 84 | this, 85 | &QBlendAnimationNode::handleInputFrameDataChanged); 86 | m_source1DestroyedConnection = connect(m_source1, 87 | &QObject::destroyed, 88 | this, 89 | [this] { setSource1(nullptr);}); 90 | } 91 | Q_EMIT source1Changed(); 92 | } 93 | 94 | QBlendTreeNode *QBlendAnimationNode::source2() const 95 | { 96 | return m_source2; 97 | } 98 | 99 | void QBlendAnimationNode::setSource2(QBlendTreeNode *newSource2) 100 | { 101 | if (m_source2 == newSource2) 102 | return; 103 | 104 | if (m_source2) { 105 | disconnect(m_source2OutputConnection); 106 | disconnect(m_source2DestroyedConnection); 107 | } 108 | 109 | m_source2 = newSource2; 110 | 111 | if (m_source2) { 112 | m_source2OutputConnection = connect(m_source2, 113 | &QBlendTreeNode::frameDataChanged, 114 | this, 115 | &QBlendAnimationNode::handleInputFrameDataChanged); 116 | m_source2DestroyedConnection = connect(m_source2, 117 | &QObject::destroyed, 118 | this, 119 | [this] { setSource2(nullptr);}); 120 | } 121 | 122 | Q_EMIT source2Changed(); 123 | } 124 | 125 | qreal QBlendAnimationNode::weight() const 126 | { 127 | return m_weight; 128 | } 129 | 130 | void QBlendAnimationNode::setWeight(qreal newWeight) 131 | { 132 | if (qFuzzyCompare(m_weight, newWeight)) 133 | return; 134 | m_weight = newWeight; 135 | Q_EMIT weightChanged(); 136 | } 137 | 138 | static QVariant lerp(const QVariant &first, const QVariant &second, float weight) 139 | { 140 | // Don't bother with weights if there is no data (for now) 141 | if (first.isNull()) 142 | return second; 143 | else if (second.isNull()) 144 | return first; 145 | 146 | const QMetaType type = first.metaType(); 147 | switch (type.id()) { 148 | case QMetaType::Bool: 149 | return QVariant((1.0f - weight) * first.toBool() + weight * second.toBool() >= 0.5f); 150 | case QMetaType::Int: 151 | return QVariant((1.0f - weight) * first.toInt() + weight * second.toInt()); 152 | case QMetaType::Float: 153 | return QVariant((1.0f - weight) * first.toFloat() + weight * second.toFloat()); 154 | case QMetaType::Double: 155 | return QVariant((1.0 - weight) * first.toDouble() + weight * second.toDouble()); 156 | case QMetaType::QVector2D: { 157 | QVector2D firstVec = first.value(); 158 | QVector2D secondVec = second.value(); 159 | return QVariant::fromValue(firstVec * (1.0f - weight) + secondVec * weight); 160 | } 161 | case QMetaType::QVector3D: { 162 | QVector3D firstVec = first.value(); 163 | QVector3D secondVec = second.value(); 164 | return QVariant::fromValue(firstVec * (1.0f - weight) + secondVec * weight); 165 | } 166 | case QMetaType::QVector4D: { 167 | QVector4D firstVec = first.value(); 168 | QVector4D secondVec = second.value(); 169 | return QVariant::fromValue(firstVec * (1.0f - weight) + secondVec * weight); 170 | } 171 | case QMetaType::QQuaternion: { 172 | QQuaternion firstQuat = first.value(); 173 | QQuaternion secondQuat = second.value(); 174 | return QVariant::fromValue(QQuaternion::nlerp(firstQuat, secondQuat, weight)); 175 | } 176 | case QMetaType::QColor: { 177 | QColor firstColor = first.value(); 178 | QColor secondColor = second.value(); 179 | int r = (1.0f - weight) * firstColor.red() + weight * secondColor.red(); 180 | int g = (1.0f - weight) * firstColor.green() + weight * secondColor.green(); 181 | int b = (1.0f - weight) * firstColor.blue() + weight * secondColor.blue(); 182 | int a = (1.0f - weight) * firstColor.alpha() + weight * secondColor.alpha(); 183 | return QVariant::fromValue(QColor(r, g, b, a)); 184 | } 185 | case QMetaType::QRect: { 186 | QRect firstRect = first.value(); 187 | QRect secondRect = second.value(); 188 | int x = (1.0f - weight) * firstRect.x() + weight * secondRect.x(); 189 | int y = (1.0f - weight) * firstRect.y() + weight * secondRect.y(); 190 | int width = (1.0f - weight) * firstRect.width() + weight * secondRect.width(); 191 | int height = (1.0f - weight) * firstRect.height() + weight * secondRect.height(); 192 | return QVariant::fromValue(QRect(x, y, width, height)); 193 | } 194 | case QMetaType::QRectF: { 195 | QRectF firstRectF = first.value(); 196 | QRectF secondRectF = second.value(); 197 | qreal x = (1.0 - weight) * firstRectF.x() + weight * secondRectF.x(); 198 | qreal y = (1.0 - weight) * firstRectF.y() + weight * secondRectF.y(); 199 | qreal width = (1.0 - weight) * firstRectF.width() + weight * secondRectF.width(); 200 | qreal height = (1.0 - weight) * firstRectF.height() + weight * secondRectF.height(); 201 | return QVariant::fromValue(QRectF(x, y, width, height)); 202 | } 203 | default: 204 | // Unsupported type, return an invalid QVariant 205 | return QVariant(); 206 | } 207 | 208 | } 209 | 210 | void QBlendAnimationNode::handleInputFrameDataChanged() 211 | { 212 | const QHash &frameData1 = m_source1 ? m_source1->frameData() : QHash(); 213 | const QHash &frameData2 = m_source2 ? m_source2->frameData() : QHash(); 214 | 215 | // Do the LERP blending here 216 | if (m_weight <= 0.0) { 217 | // all source1 218 | m_frameData = frameData1; 219 | } else if (m_weight >= 1.0) { 220 | // all source2 221 | m_frameData = frameData2; 222 | } else { 223 | // is a mix 224 | QHash> allData; 225 | const auto &keys1 = frameData1.keys(); 226 | for (const auto &property : keys1) 227 | allData.insert(property, QPair(frameData1[property], QVariant())); 228 | const auto &keys2 = frameData2.keys(); 229 | for (const auto &property : keys2) { 230 | // first check if property is already in all data, and if so modify the pair to include this value 231 | if (allData.contains(property)) { 232 | allData[property].second = frameData2[property]; 233 | } else { 234 | allData.insert(property, QPair(QVariant(), frameData2[property])); 235 | } 236 | } 237 | 238 | QHash newFrameData; 239 | 240 | const auto &keys = allData.keys(); 241 | for (const auto &property : keys) { 242 | const auto &dataPair = allData[property]; 243 | newFrameData.insert(property, lerp(dataPair.first, dataPair.second, m_weight)); 244 | } 245 | m_frameData = newFrameData; 246 | } 247 | 248 | Q_EMIT frameDataChanged(); 249 | } 250 | 251 | QT_END_NAMESPACE 252 | -------------------------------------------------------------------------------- /tests/auto/qtquicktimeline_blendtrees/tst_blendtrees.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | inline QUrl testFileUrl(const QString &fileName) 12 | { 13 | static const QString dir = QTest::qFindTestData("data"); 14 | 15 | QString result = dir; 16 | result += QLatin1Char('/'); 17 | result += fileName; 18 | 19 | return QUrl::fromLocalFile(result); 20 | } 21 | 22 | class Tst_BlendTrees : public QObject 23 | { 24 | Q_OBJECT 25 | 26 | private Q_SLOTS: 27 | void checkImport(); 28 | void testBlendAnimationNode(); 29 | 30 | }; 31 | 32 | 33 | 34 | void Tst_BlendTrees::checkImport() 35 | { 36 | QQmlEngine engine; 37 | QQmlComponent component(&engine); 38 | component.setData("import QtQuick; import QtQuick.Timeline; import QtQuick.Timeline.BlendTrees; Item { }", QUrl()); 39 | 40 | QScopedPointer object(component.create()); 41 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); 42 | } 43 | 44 | void Tst_BlendTrees::testBlendAnimationNode() 45 | { 46 | QQmlEngine engine; 47 | QQmlComponent component(&engine); 48 | component.loadUrl(testFileUrl("BlendTreeTest.qml")); 49 | 50 | QScopedPointer object(component.create()); 51 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); 52 | 53 | // Get all of the necessary objects 54 | auto *timeline = object->findChild("timeline"); 55 | auto *timelineAnimation1 = object->findChild("animation1"); 56 | QVERIFY2(timelineAnimation1, "Could not find animation1"); 57 | auto *timelineAnimation2 = object->findChild("animation2"); 58 | QVERIFY2(timelineAnimation2, "Could not find animation2"); 59 | auto *animation1Node = object->findChild("animation1Node"); 60 | QVERIFY2(animation1Node, "Could not find animation1Node"); 61 | auto *animation2Node = object->findChild("animation2Node"); 62 | QVERIFY2(animation2Node, "Could not find animation2Node"); 63 | auto *blendAnimation = object->findChild("blendAnimation"); 64 | QVERIFY2(blendAnimation, "Could not find blendAnimation"); 65 | auto *rectangle = object->findChild("rectangle"); 66 | QVERIFY2(rectangle, "Could not find rectangle"); 67 | auto *animation1Controller = object->findChild("animation1Controller"); 68 | QVERIFY2(animation1Controller, "Could not find animation1Controller"); 69 | auto *animation2Controller = object->findChild("animation2Controller"); 70 | QVERIFY2(animation2Controller, "Could not find animation2Controller"); 71 | 72 | 73 | // At this point nothing should be happening because the animations have controllers 74 | // attached to this which forces them to always be paused. Starting states is: 75 | // animation1Node.currentFrame == 0 76 | // animation2Node.currentFrame == 100 77 | // rectangle.x = 100 78 | // rectangle.y = 100 79 | // rectangle.color = #ff7f00 80 | 81 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 0); 82 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 100); 83 | QCOMPARE(rectangle->property("x").toInt(), 100); 84 | QCOMPARE(rectangle->property("y").toInt(), 100); 85 | QCOMPARE(rectangle->property("color").value(), QColor("#ff7f00")); 86 | 87 | // Push animation1 to end 88 | // Should be a blend of 50% blend of animation1 and and 50% blend of animation2 89 | // animation 1 should be at frame 100 (100%) 90 | // animation 2 should be at frame 100 (0%) 91 | animation1Controller->setProperty("progress", 1.0f); 92 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 100); 93 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 100); 94 | QCOMPARE(rectangle->property("x").toInt(), 200); 95 | QCOMPARE(rectangle->property("y").toInt(), 200); 96 | QCOMPARE(rectangle->property("color").value(), QColor("#ffff00")); 97 | 98 | // Push animation2 to end 99 | // Should be a blend of 50% blend of animation1 and and 50% blend of animation2 100 | // animation 1 should be at frame 100 (100%) 101 | // animation 2 should be at frame 200 (100%) 102 | animation2Controller->setProperty("progress", 1.0f); 103 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 100); 104 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 200); 105 | QCOMPARE(rectangle->property("x").toInt(), 100); 106 | QCOMPARE(rectangle->property("y").toInt(), 300); 107 | QCOMPARE(rectangle->property("color").value(), QColor("#ff7f7f")); 108 | 109 | // Change weight to 0.0 110 | // Should be a blend of 100% blend of animation1 and and 0% blend of animation2 111 | // animation 1 should be at frame 100 (100%) 112 | // animation 2 should be at frame 200 (100%) 113 | blendAnimation->setProperty("weight", 0.0f); 114 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 100); 115 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 200); 116 | QCOMPARE(rectangle->property("x").toInt(), 200); 117 | QCOMPARE(rectangle->property("y").toInt(), 200); 118 | QCOMPARE(rectangle->property("color").value(), QColor("#ffff00")); 119 | 120 | // Change weight to 1.0 121 | // Should be a blend of 0% blend of animation1 and and 100% blend of animation2 122 | // animation 1 should be at frame 100 (100%) 123 | // animation 2 should be at frame 200 (100%) 124 | blendAnimation->setProperty("weight", 1.0f); 125 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 100); 126 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 200); 127 | QCOMPARE(rectangle->property("x").toInt(), 0); 128 | QCOMPARE(rectangle->property("y").toInt(), 400); 129 | QCOMPARE(rectangle->property("color").value(), QColor("#ff00ff")); 130 | 131 | // Change animation1 to start (should be the same as previous) 132 | // Should be a blend of 0% blend of animation1 and and 100% blend of animation2 133 | // animation 1 should be at frame 0 (0%) 134 | // animation 2 should be at frame 200 (100%) 135 | animation1Controller->setProperty("progress", 0.0f); 136 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 0); 137 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 200); 138 | QCOMPARE(rectangle->property("x").toInt(), 0); 139 | QCOMPARE(rectangle->property("y").toInt(), 400); 140 | QCOMPARE(rectangle->property("color").value(), QColor("#ff00ff")); 141 | 142 | // Change weight to 0.0 143 | // Should be a blend of 100% blend of animation1 and and 0% blend of animation2 144 | // animation 1 should be at frame 0 (0%) 145 | // animation 2 should be at frame 200 (100%) 146 | blendAnimation->setProperty("weight", 0.0f); 147 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 0); 148 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 200); 149 | QCOMPARE(rectangle->property("x").toInt(), 0); 150 | QCOMPARE(rectangle->property("y").toInt(), 0); 151 | QCOMPARE(rectangle->property("color").value(), QColor("#ff0000")); 152 | 153 | // Change animation2 to start (should be the same as previous) 154 | // Should be a blend of 100% blend of animation1 and and 0% blend of animation2 155 | // animation 1 should be at frame 0 (0%) 156 | // animation 2 should be at frame 100 (0%) 157 | animation2Controller->setProperty("progress", 0.0f); 158 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 0); 159 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 100); 160 | QCOMPARE(rectangle->property("x").toInt(), 0); 161 | QCOMPARE(rectangle->property("y").toInt(), 0); 162 | QCOMPARE(rectangle->property("color").value(), QColor("#ff0000")); 163 | 164 | // Disable committing of changes by animationBlendNode 165 | blendAnimation->setProperty("outputEnabled", false); 166 | // Now changing either animation progress or weight should have no effect on the scene 167 | animation1Controller->setProperty("progress", 1.0f); 168 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 100); 169 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 100); 170 | QCOMPARE(rectangle->property("x").toInt(), 0); 171 | QCOMPARE(rectangle->property("y").toInt(), 0); 172 | QCOMPARE(rectangle->property("color").value(), QColor("#ff0000")); 173 | 174 | animation2Controller->setProperty("progress", 1.0f); 175 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 100); 176 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 200); 177 | QCOMPARE(rectangle->property("x").toInt(), 0); 178 | QCOMPARE(rectangle->property("y").toInt(), 0); 179 | QCOMPARE(rectangle->property("color").value(), QColor("#ff0000")); 180 | 181 | blendAnimation->setProperty("weight", 0.5f); 182 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 100); 183 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 200); 184 | QCOMPARE(rectangle->property("x").toInt(), 0); 185 | QCOMPARE(rectangle->property("y").toInt(), 0); 186 | QCOMPARE(rectangle->property("color").value(), QColor("#ff0000")); 187 | 188 | // re-enable committing of changes by animationBlendNode 189 | // This should cause the animation to blend to the new state 190 | blendAnimation->setProperty("outputEnabled", true); 191 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 100); 192 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 200); 193 | QCOMPARE(rectangle->property("x").toInt(), 100); 194 | QCOMPARE(rectangle->property("y").toInt(), 300); 195 | QCOMPARE(rectangle->property("color").value(), QColor("#ff7f7f")); 196 | 197 | // Test disconnecting animation1 198 | blendAnimation->setProperty("source1", QVariant()); 199 | 200 | animation1Controller->setProperty("progress", 0.0f); 201 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 0); 202 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 200); 203 | QCOMPARE(rectangle->property("x").toInt(), 100); 204 | QCOMPARE(rectangle->property("y").toInt(), 300); 205 | QCOMPARE(rectangle->property("color").value(), QColor("#ff7f7f")); 206 | 207 | // Test disconnecting animation2 208 | blendAnimation->setProperty("source2", QVariant()); 209 | animation2Controller->setProperty("progress", 0.0f); 210 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 0); 211 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 100); 212 | QCOMPARE(rectangle->property("x").toInt(), 100); 213 | QCOMPARE(rectangle->property("y").toInt(), 300); 214 | QCOMPARE(rectangle->property("color").value(), QColor("#ff7f7f")); 215 | 216 | // Disable committing of changes by animationBlendNode 217 | blendAnimation->setProperty("outputEnabled", false); 218 | 219 | // Try outputting dirrectly from animation1 220 | animation1Node->setProperty("outputEnabled", true); 221 | // Should have changed to be the value of animation1 at frame 0 222 | QCOMPARE(rectangle->property("x").toInt(), 0); 223 | QCOMPARE(rectangle->property("y").toInt(), 0); 224 | QCOMPARE(rectangle->property("color").value(), QColor("#ff0000")); 225 | 226 | animation1Controller->setProperty("progress", 1.0f); 227 | // Should have changed to be the value of animation1 at frame 100 228 | QCOMPARE(rectangle->property("x").toInt(), 200); 229 | QCOMPARE(rectangle->property("y").toInt(), 200); 230 | QCOMPARE(rectangle->property("color").value(), QColor("#ffff00")); 231 | 232 | // Disable outputting dirrectly from animation1 233 | animation1Node->setProperty("outputEnabled", false); 234 | // Try outputting dirrectly from animation2 235 | animation2Node->setProperty("outputEnabled", true); 236 | // Nothing should have changed since both would be at frame 100 anyway 237 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 100); 238 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 100); 239 | QCOMPARE(rectangle->property("x").toInt(), 200); 240 | QCOMPARE(rectangle->property("y").toInt(), 200); 241 | QCOMPARE(rectangle->property("color").value(), QColor("#ffff00")); 242 | 243 | animation2Controller->setProperty("progress", 1.0f); 244 | QCOMPARE(animation1Node->property("currentFrame").toInt(), 100); 245 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 200); 246 | QCOMPARE(rectangle->property("x").toInt(), 0); 247 | QCOMPARE(rectangle->property("y").toInt(), 400); 248 | QCOMPARE(rectangle->property("color").value(), QColor("#ff00ff")); 249 | 250 | // Try breaking animation2Node's connection to timeline (the source of frame data) 251 | // currentFrame should change but the output should not 252 | animation2Node->setProperty("timeline", QVariant()); 253 | animation2Controller->setProperty("progress", 0.0f); 254 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 100); 255 | QCOMPARE(rectangle->property("x").toInt(), 0); 256 | QCOMPARE(rectangle->property("y").toInt(), 400); 257 | QCOMPARE(rectangle->property("color").value(), QColor("#ff00ff")); 258 | 259 | // reattach the timeline 260 | animation2Node->setProperty("timeline", QVariant::fromValue(timeline)); 261 | // Should update the output now that we can fetch frameData for frame 100 262 | QCOMPARE(animation2Node->property("currentFrame").toInt(), 100); 263 | QCOMPARE(rectangle->property("x").toInt(), 200); 264 | QCOMPARE(rectangle->property("y").toInt(), 200); 265 | QCOMPARE(rectangle->property("color").value(), QColor("#ffff00")); 266 | 267 | // Try breaking animation2Node's connection to animation 268 | animation2Node->setProperty("animation", QVariant()); 269 | animation2Controller->setProperty("progress", 1.0f); 270 | QCOMPARE(rectangle->property("x").toInt(), 200); 271 | QCOMPARE(rectangle->property("y").toInt(), 200); 272 | QCOMPARE(rectangle->property("color").value(), QColor("#ffff00")); 273 | 274 | // reattach the animation 275 | animation2Node->setProperty("animation", QVariant::fromValue(timelineAnimation2)); 276 | // Should update the output now that we can fetch frameData for frame 200 277 | QCOMPARE(rectangle->property("x").toInt(), 0); 278 | QCOMPARE(rectangle->property("y").toInt(), 400); 279 | QCOMPARE(rectangle->property("color").value(), QColor("#ff00ff")); 280 | } 281 | 282 | QTEST_MAIN(Tst_BlendTrees) 283 | 284 | #include "tst_blendtrees.moc" 285 | -------------------------------------------------------------------------------- /tests/auto/qtquicktimeline/tst_qtquicktimeline.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | class Tst_QtQuickTimeline : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | private Q_SLOTS: 16 | void checkImport(); 17 | void simpleTest(); 18 | void parameterization(); 19 | void vectors(); 20 | void deltaFunction(); 21 | void keyframeUpdate(); 22 | void easingcurveInterpolation(); 23 | void restoreBindingTest(); 24 | }; 25 | 26 | inline QUrl testFileUrl(const QString &fileName) 27 | { 28 | static const QString dir = QTest::qFindTestData("data"); 29 | 30 | QString result = dir; 31 | result += QLatin1Char('/'); 32 | result += fileName; 33 | 34 | return QUrl::fromLocalFile(result); 35 | } 36 | 37 | void Tst_QtQuickTimeline::checkImport() 38 | { 39 | QQmlEngine engine; 40 | QQmlComponent component(&engine); 41 | component.setData("import QtQuick 2.0; import QtQuick.Timeline 1.0; Item { }", QUrl()); 42 | 43 | QScopedPointer object(component.create()); 44 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); 45 | } 46 | 47 | void Tst_QtQuickTimeline::simpleTest() 48 | { 49 | QQmlEngine engine; 50 | QQmlComponent component(&engine); 51 | component.loadUrl(testFileUrl("simpletest.qml")); 52 | 53 | QScopedPointer object(component.create()); 54 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); 55 | 56 | auto *rectangle = object->findChild("rectangle"); 57 | QVERIFY(rectangle); 58 | 59 | QCOMPARE(rectangle->property("width").toInt(), 20); 60 | QCOMPARE(rectangle->property("height").toInt(), 20); 61 | QCOMPARE(rectangle->property("color").value(), QColor("blue")); 62 | QCOMPARE(rectangle->property("x").toInt(), 100); 63 | QCOMPARE(rectangle->property("y").toInt(), 100); 64 | 65 | auto *timeline = object->findChild("timeline"); 66 | QVERIFY(timeline); 67 | 68 | QCOMPARE(timeline->property("enabled").toBool(), true); 69 | QCOMPARE(timeline->property("startFrame").toInt(), 0); 70 | QCOMPARE(timeline->property("endFrame").toInt(), 100); 71 | QCOMPARE(timeline->property("currentFrame").toInt(), 50); 72 | 73 | auto *animation = object->findChild("animation"); 74 | QVERIFY(animation); 75 | 76 | QCOMPARE(animation->property("running").toBool(), false); 77 | QCOMPARE(animation->property("duration").toInt(), 200); 78 | QCOMPARE(animation->property("loops").toInt(), 1); 79 | QCOMPARE(animation->property("from").toInt(), 0); 80 | QCOMPARE(animation->property("to").toInt(), 100); 81 | 82 | auto *group = object->findChild("group01"); 83 | QVERIFY(group); 84 | 85 | QCOMPARE(group->property("target").value(), rectangle); 86 | 87 | timeline->setProperty("currentFrame", 0); 88 | 89 | QCOMPARE(rectangle->property("color").value(), QColor("red")); 90 | QCOMPARE(rectangle->property("x").toInt(), 0); 91 | QCOMPARE(rectangle->property("y").toInt(), 0); 92 | 93 | timeline->setProperty("currentFrame", 100); 94 | 95 | QCOMPARE(rectangle->property("color").value(), QColor("yellow")); 96 | QCOMPARE(rectangle->property("x").toInt(), 200); 97 | QCOMPARE(rectangle->property("y").toInt(), 200); 98 | 99 | timeline->setProperty("enabled", false); 100 | QCOMPARE(rectangle->property("color").value(), QColor("red")); 101 | QCOMPARE(rectangle->property("x").toInt(), 0); 102 | QCOMPARE(rectangle->property("y").toInt(), 0); 103 | 104 | timeline->setProperty("currentFrame", 0); 105 | timeline->setProperty("enabled", true); 106 | 107 | animation->setProperty("running", true); 108 | QCOMPARE(animation->property("running").toBool(), true); 109 | } 110 | 111 | void Tst_QtQuickTimeline::parameterization() 112 | { 113 | QQmlEngine engine; 114 | QQmlComponent component(&engine); 115 | component.loadUrl(testFileUrl("parameterization.qml")); 116 | 117 | QScopedPointer object(component.create()); 118 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); 119 | 120 | auto *timeline = object->findChild("timeline"); 121 | QVERIFY(timeline); 122 | 123 | QCOMPARE(timeline->property("enabled").toBool(), true); 124 | QCOMPARE(timeline->property("startFrame").toInt(), 0); 125 | QCOMPARE(timeline->property("endFrame").toInt(), 200); 126 | QCOMPARE(timeline->property("currentFrame").toInt(), 10); 127 | 128 | auto *needle = object->findChild("needle"); 129 | QVERIFY(needle); 130 | 131 | QCOMPARE(needle->property("width").toInt(), 150); 132 | QCOMPARE(needle->property("height").toInt(), 4); 133 | QCOMPARE(needle->property("x").toInt(), 0); 134 | QCOMPARE(needle->property("y").toInt(), 148); 135 | 136 | auto *group = object->findChild("group01"); 137 | QVERIFY(group); 138 | 139 | QCOMPARE(group->property("target").value(), needle); 140 | 141 | auto *textInput = object->findChild("textInput"); 142 | QVERIFY(textInput); 143 | 144 | QCOMPARE(textInput->property("text").toString(), "10"); 145 | 146 | textInput->setProperty("text", "0"); 147 | 148 | QCOMPARE(needle->property("color").value(), QColor("blue")); 149 | 150 | timeline->setProperty("enabled", false); 151 | QCOMPARE(needle->property("color").value(), QColor("#c41616")); 152 | QCOMPARE(needle->property("rotation").toInt(), 0); 153 | timeline->setProperty("enabled", true); 154 | 155 | textInput->setProperty("text", "100"); 156 | QCOMPARE(needle->property("color").value(), QColor("green")); 157 | QCOMPARE(needle->property("rotation").toInt(), 90); 158 | 159 | 160 | textInput->setProperty("text", "200"); 161 | QCOMPARE(needle->property("color").value(), QColor("red")); 162 | QCOMPARE(needle->property("rotation").toInt(), 180); 163 | } 164 | 165 | 166 | void Tst_QtQuickTimeline::Tst_QtQuickTimeline::vectors() 167 | { 168 | QQmlEngine engine; 169 | QQmlComponent component(&engine); 170 | component.loadUrl(testFileUrl("vectors.qml")); 171 | 172 | QScopedPointer object(component.create()); 173 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); 174 | 175 | auto *timeline = object->findChild("timeline"); 176 | QVERIFY(timeline); 177 | 178 | QCOMPARE(timeline->property("enabled").toBool(), true); 179 | QCOMPARE(timeline->property("startFrame").toInt(), 0); 180 | QCOMPARE(timeline->property("endFrame").toInt(), 200); 181 | QCOMPARE(timeline->property("currentFrame").toInt(), 0); 182 | 183 | auto *rotation = object->findChild("rotation"); 184 | QVERIFY(rotation); 185 | 186 | auto vector = rotation->property("origin").value(); 187 | 188 | QVERIFY(!vector.isNull()); 189 | QCOMPARE(vector.x(), 0.0); 190 | QCOMPARE(vector.y(), 30.0); 191 | 192 | vector = rotation->property("axis").value(); 193 | 194 | QVERIFY(!vector.isNull()); 195 | QCOMPARE(vector.x(), 0.0); 196 | QCOMPARE(vector.y(), 1.0); 197 | QCOMPARE(vector.z(), 0.0); 198 | 199 | timeline->setProperty("currentFrame", 50); 200 | 201 | vector = rotation->property("axis").value(); 202 | 203 | QVERIFY(!vector.isNull()); 204 | QCOMPARE(vector.x(), 1.0); 205 | QCOMPARE(vector.y(), 1.0); 206 | QCOMPARE(vector.z(), 0.0); 207 | 208 | vector = rotation->property("origin").value(); 209 | 210 | QVERIFY(!vector.isNull()); 211 | QCOMPARE(vector.x(), 10.0); 212 | QCOMPARE(vector.y(), 30.0); 213 | 214 | timeline->setProperty("currentFrame", 100); 215 | 216 | vector = rotation->property("axis").value(); 217 | 218 | QVERIFY(!vector.isNull()); 219 | QCOMPARE(vector.x(), 0.0); 220 | QCOMPARE(vector.y(), 1.0); 221 | QCOMPARE(vector.z(), 0.0); 222 | 223 | vector = rotation->property("origin").value(); 224 | 225 | QVERIFY(!vector.isNull()); 226 | QCOMPARE(vector.x(), 20.0); 227 | QCOMPARE(vector.y(), 30.0); 228 | 229 | timeline->setProperty("enabled", false); 230 | 231 | vector = rotation->property("origin").value(); 232 | 233 | QVERIFY(!vector.isNull()); 234 | QCOMPARE(vector.x(), 30.0); 235 | QCOMPARE(vector.y(), 30.0); 236 | } 237 | 238 | void Tst_QtQuickTimeline::deltaFunction() 239 | { 240 | QQmlEngine engine; 241 | QQmlComponent component(&engine); 242 | component.loadUrl(testFileUrl("deltafunction.qml")); 243 | 244 | QScopedPointer object(component.create()); 245 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); 246 | 247 | auto *timeline = object->findChild("timeline"); 248 | QVERIFY(timeline); 249 | 250 | QCOMPARE(timeline->property("enabled").toBool(), true); 251 | QCOMPARE(timeline->property("startFrame").toInt(), 0); 252 | QCOMPARE(timeline->property("endFrame").toInt(), 100); 253 | QCOMPARE(timeline->property("currentFrame").toInt(), 0); 254 | 255 | auto *text = object->findChild("text"); 256 | QVERIFY(text); 257 | 258 | QCOMPARE(text->property("text").toString(), "frame0"); 259 | 260 | auto *group = object->findChild("group01"); 261 | QVERIFY(group); 262 | 263 | QCOMPARE(group->property("target").value(), text); 264 | 265 | timeline->setProperty("enabled", false); 266 | QCOMPARE(text->property("text").toString(), "no timeline"); 267 | 268 | timeline->setProperty("enabled", true); 269 | QCOMPARE(text->property("text").toString(), "frame0"); 270 | 271 | timeline->setProperty("currentFrame", 49); 272 | QCOMPARE(text->property("text").toString(), "frame0"); 273 | 274 | timeline->setProperty("currentFrame", 50); 275 | QCOMPARE(text->property("text").toString(), "frame50"); 276 | 277 | timeline->setProperty("currentFrame", 51); 278 | QCOMPARE(text->property("text").toString(), "frame50"); 279 | 280 | timeline->setProperty("currentFrame", 49); 281 | QCOMPARE(text->property("text").toString(), "frame0"); 282 | 283 | timeline->setProperty("currentFrame", 99); 284 | QCOMPARE(text->property("text").toString(), "frame50"); 285 | 286 | timeline->setProperty("currentFrame", 100); 287 | QCOMPARE(text->property("text").toString(), "frame100"); 288 | 289 | timeline->setProperty("currentFrame", 101); 290 | QCOMPARE(text->property("text").toString(), "frame100"); 291 | } 292 | 293 | void Tst_QtQuickTimeline::keyframeUpdate() 294 | { 295 | QQmlEngine engine; 296 | QQmlComponent component(&engine); 297 | component.loadUrl(testFileUrl("simpletest.qml")); 298 | 299 | QScopedPointer object(component.create()); 300 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); 301 | 302 | auto *timeline = object->findChild("timeline"); 303 | QVERIFY(timeline); 304 | 305 | QCOMPARE(timeline->property("enabled").toBool(), true); 306 | QCOMPARE(timeline->property("startFrame").toInt(), 0); 307 | QCOMPARE(timeline->property("endFrame").toInt(), 100); 308 | QCOMPARE(timeline->property("currentFrame").toInt(), 50); 309 | 310 | auto *rectangle = object->findChild("rectangle"); 311 | QVERIFY(rectangle); 312 | 313 | QCOMPARE(rectangle->property("x").toInt(), 100); 314 | 315 | auto *keyframe = object->findChild("keyframe"); 316 | QVERIFY(keyframe); 317 | 318 | QCOMPARE(keyframe->property("frame").toInt(), 50); 319 | QCOMPARE(keyframe->property("value").toInt(), 100); 320 | 321 | keyframe->setProperty("value", 70); 322 | QCOMPARE(keyframe->property("value").toInt(), 70); 323 | QCOMPARE(rectangle->property("x").toInt(), 70); 324 | 325 | keyframe->setProperty("value", 90); 326 | QCOMPARE(keyframe->property("value").toInt(), 90); 327 | QCOMPARE(rectangle->property("x").toInt(), 90); 328 | 329 | timeline->setProperty("currentFrame", 60); 330 | QCOMPARE(timeline->property("currentFrame").toInt(), 60); 331 | 332 | QVERIFY(rectangle->property("x").toInt() != 90); 333 | keyframe->setProperty("frame", 60); 334 | QCOMPARE(rectangle->property("x").toInt(), 90); 335 | } 336 | 337 | void Tst_QtQuickTimeline::easingcurveInterpolation() 338 | { 339 | QQmlEngine engine; 340 | QQmlComponent component(&engine); 341 | component.loadUrl(testFileUrl("simpletest.qml")); 342 | 343 | QScopedPointer object(component.create()); 344 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); 345 | 346 | auto *timeline = object->findChild("timeline"); 347 | QVERIFY(timeline); 348 | 349 | QCOMPARE(timeline->property("enabled").toBool(), true); 350 | QCOMPARE(timeline->property("startFrame").toInt(), 0); 351 | QCOMPARE(timeline->property("endFrame").toInt(), 100); 352 | QCOMPARE(timeline->property("currentFrame").toInt(), 50); 353 | 354 | auto *rectangle = object->findChild("rectangle"); 355 | QVERIFY(rectangle); 356 | 357 | QCOMPARE(rectangle->property("y").toInt(), 100); 358 | 359 | auto *keyframe = object->findChild("easingBounce"); 360 | QVERIFY(keyframe); 361 | 362 | QCOMPARE(keyframe->property("frame").toInt(), 100); 363 | QCOMPARE(keyframe->property("value").toInt(), 200); 364 | 365 | auto easingCurve = keyframe->property("easing").value(); 366 | QCOMPARE(easingCurve.type(), QEasingCurve::InBounce); 367 | 368 | timeline->setProperty("currentFrame", 100); 369 | QCOMPARE(rectangle->property("y").toInt(), 200); 370 | 371 | timeline->setProperty("currentFrame", 75); 372 | qreal progress = easingCurve.valueForProgress(0.5); 373 | qreal final = (1 - progress) * 100 + progress * 200; 374 | QCOMPARE(rectangle->property("y").toReal(), final); 375 | 376 | timeline->setProperty("currentFrame", 90); 377 | progress = easingCurve.valueForProgress(0.8); 378 | final = (1 - progress) * 100 + progress * 200; 379 | QCOMPARE(rectangle->property("y").toReal(), final); 380 | 381 | timeline->setProperty("currentFrame", 95); 382 | progress = easingCurve.valueForProgress(0.9); 383 | final = (1 - progress) * 100 + progress * 200; 384 | QCOMPARE(rectangle->property("y").toReal(), final); 385 | 386 | timeline->setProperty("currentFrame", 55); 387 | progress = easingCurve.valueForProgress(0.1); 388 | final = (1 - progress) * 100 + progress * 200; 389 | QCOMPARE(rectangle->property("y").toReal(), final); 390 | 391 | timeline->setProperty("currentFrame", 60); 392 | progress = easingCurve.valueForProgress(0.2); 393 | final = (1 - progress) * 100 + progress * 200; 394 | QCOMPARE(rectangle->property("y").toReal(), final); 395 | } 396 | 397 | void Tst_QtQuickTimeline::restoreBindingTest() 398 | { 399 | QQmlEngine engine; 400 | QQmlComponent component(&engine); 401 | component.loadUrl(testFileUrl("restorebindingtest.qml")); 402 | 403 | QScopedPointer object(component.create()); 404 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); 405 | 406 | auto *rectangle = object->findChild("rectangle"); 407 | QVERIFY(rectangle); 408 | 409 | QCOMPARE(rectangle->property("offset").toInt(), 0); 410 | QCOMPARE(rectangle->property("x").toInt(), 0); 411 | QCOMPARE(rectangle->property("y").toInt(), 0); 412 | 413 | auto *timeline = object->findChild("timeline"); 414 | QVERIFY(timeline); 415 | 416 | QCOMPARE(timeline->property("enabled").toBool(), false); 417 | QCOMPARE(timeline->property("startFrame").toInt(), 0); 418 | QCOMPARE(timeline->property("endFrame").toInt(), 100); 419 | QCOMPARE(timeline->property("currentFrame").toInt(), 50); 420 | 421 | // currentFrame == 50 422 | timeline->setProperty("enabled", true); 423 | QCOMPARE(rectangle->property("x").toInt(), 70); 424 | QCOMPARE(rectangle->property("y").toInt(), 70); 425 | 426 | timeline->setProperty("currentFrame", 0); 427 | QCOMPARE(rectangle->property("x").toInt(), 140); 428 | QCOMPARE(rectangle->property("y").toInt(), 0); 429 | 430 | timeline->setProperty("currentFrame", 100); 431 | QCOMPARE(rectangle->property("x").toInt(), 0); 432 | QCOMPARE(rectangle->property("y").toInt(), 140); 433 | 434 | 435 | timeline->setProperty("enabled", false); 436 | // check restoring of the original binding 437 | QCOMPARE(rectangle->property("x").toInt(), 0); 438 | QCOMPARE(rectangle->property("y").toInt(), 0); 439 | rectangle->setProperty("offset", 50); 440 | QCOMPARE(rectangle->property("x").toInt(), 50); 441 | QCOMPARE(rectangle->property("y").toInt(), 50); 442 | 443 | timeline->setProperty("enabled", true); 444 | // currentFrame == 100, offset == 50 445 | QCOMPARE(rectangle->property("x").toInt(), 50); 446 | QCOMPARE(rectangle->property("y").toInt(), 140); 447 | } 448 | QTEST_MAIN(Tst_QtQuickTimeline) 449 | 450 | #include "tst_qtquicktimeline.moc" 451 | -------------------------------------------------------------------------------- /src/timeline/qquickkeyframe.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include "qquickkeyframe_p.h" 5 | 6 | #include "qquicktimeline_p.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | QT_BEGIN_NAMESPACE 32 | 33 | class QQuickKeyframeGroupPrivate : public QObjectPrivate 34 | { 35 | Q_DECLARE_PUBLIC(QQuickKeyframeGroup) 36 | public: 37 | QQuickKeyframeGroupPrivate() = default; 38 | 39 | QObject *target = nullptr; 40 | QString propertyName; 41 | QUrl keyframeSource; 42 | QByteArray keyframeData; 43 | bool componentComplete = false; 44 | int userType = -1; 45 | 46 | protected: 47 | void setupKeyframes(); 48 | bool loadKeyframes(bool fromBinary = false); 49 | void resetKeyframes(); 50 | 51 | static void append_keyframe(QQmlListProperty *list, QQuickKeyframe *a); 52 | static qsizetype keyframe_count(QQmlListProperty *list); 53 | static QQuickKeyframe* keyframe_at(QQmlListProperty *list, qsizetype pos); 54 | static void clear_keyframes(QQmlListProperty *list); 55 | 56 | QList keyframes; 57 | QList sortedKeyframes; 58 | 59 | QVariant originalValue; 60 | QVariant lastValue; 61 | QQmlAnyBinding originalBinding; 62 | }; 63 | 64 | void QQuickKeyframeGroupPrivate::setupKeyframes() 65 | { 66 | sortedKeyframes = keyframes; 67 | std::sort(sortedKeyframes.begin(), sortedKeyframes.end(), [](const QQuickKeyframe *first, const QQuickKeyframe *second) { 68 | return first->frame() < second->frame(); 69 | }); 70 | } 71 | 72 | // time, easingType, data 73 | static int validFrameSize(QMetaType::Type type) 74 | { 75 | switch (type) { 76 | case QMetaType::Bool: 77 | case QMetaType::Int: 78 | case QMetaType::Float: 79 | case QMetaType::Double: 80 | return 3; 81 | case QMetaType::QVector2D: 82 | case QMetaType::QPoint: 83 | case QMetaType::QPointF: 84 | case QMetaType::QSize: 85 | case QMetaType::QSizeF: 86 | return 4; 87 | case QMetaType::QVector3D: 88 | return 5; 89 | case QMetaType::QVector4D: 90 | case QMetaType::QQuaternion: 91 | case QMetaType::QColor: 92 | case QMetaType::QRect: 93 | case QMetaType::QRectF: 94 | return 6; 95 | default: 96 | qWarning() << "Keyframe property type not handled:" << type; 97 | return -1; 98 | } 99 | } 100 | 101 | // Read property 'type' value from CborArray from index 'id' and return it as QVariant. 102 | static QVariant qQuickKeyframeReadProperty(const QCborArray &array, qsizetype id, QMetaType::Type type) 103 | { 104 | switch (type) { 105 | case QMetaType::QVector2D: 106 | { 107 | QVector2D v; 108 | v.setX(array.at(id).toDouble()); 109 | v.setY(array.at(id + 1).toDouble()); 110 | return QVariant(v); 111 | } 112 | break; 113 | case QMetaType::QVector3D: 114 | { 115 | QVector3D v; 116 | v.setX(array.at(id).toDouble()); 117 | v.setY(array.at(id + 1).toDouble()); 118 | v.setZ(array.at(id + 2).toDouble()); 119 | return QVariant(v); 120 | } 121 | break; 122 | case QMetaType::QVector4D: 123 | { 124 | QVector4D v; 125 | v.setX(array.at(id).toDouble()); 126 | v.setY(array.at(id + 1).toDouble()); 127 | v.setZ(array.at(id + 2).toDouble()); 128 | v.setW(array.at(id + 3).toDouble()); 129 | return QVariant(v); 130 | } 131 | break; 132 | case QMetaType::QQuaternion: 133 | { 134 | QQuaternion q; 135 | q.setScalar(array.at(id).toDouble()); 136 | q.setX(array.at(id + 1).toDouble()); 137 | q.setY(array.at(id + 2).toDouble()); 138 | q.setZ(array.at(id + 3).toDouble()); 139 | return QVariant(q); 140 | } 141 | break; 142 | case QMetaType::QColor: 143 | { 144 | QColor c; 145 | c.setRed(array.at(id).toInteger()); 146 | c.setGreen(array.at(id + 1).toInteger()); 147 | c.setBlue(array.at(id + 2).toInteger()); 148 | c.setAlpha(array.at(id + 3).toInteger()); 149 | return QVariant(c); 150 | } 151 | break; 152 | case QMetaType::QRectF: 153 | { 154 | QRectF r; 155 | r.setX(array.at(id).toDouble()); 156 | r.setY(array.at(id + 1).toDouble()); 157 | r.setWidth(array.at(id + 2).toDouble()); 158 | r.setHeight(array.at(id + 3).toDouble()); 159 | return QVariant(r); 160 | } 161 | break; 162 | case QMetaType::QRect: 163 | { 164 | QRect r; 165 | r.setX(array.at(id).toInteger()); 166 | r.setY(array.at(id + 1).toInteger()); 167 | r.setWidth(array.at(id + 2).toInteger()); 168 | r.setHeight(array.at(id + 3).toInteger()); 169 | return QVariant(r); 170 | } 171 | break; 172 | case QMetaType::QPointF: 173 | { 174 | QPointF p; 175 | p.setX(array.at(id).toDouble()); 176 | p.setY(array.at(id + 1).toDouble()); 177 | return QVariant(p); 178 | } 179 | break; 180 | case QMetaType::QPoint: 181 | { 182 | QPoint p; 183 | p.setX(array.at(id).toInteger()); 184 | p.setY(array.at(id + 1).toInteger()); 185 | return QVariant(p); 186 | } 187 | break; 188 | case QMetaType::QSizeF: 189 | { 190 | QSizeF s; 191 | s.setWidth(array.at(id).toDouble()); 192 | s.setHeight(array.at(id + 1).toDouble()); 193 | return QVariant(s); 194 | } 195 | break; 196 | case QMetaType::QSize: 197 | { 198 | QSize s; 199 | s.setWidth(array.at(id).toInteger()); 200 | s.setHeight(array.at(id + 1).toInteger()); 201 | return QVariant(s); 202 | } 203 | break; 204 | case QMetaType::Bool: 205 | case QMetaType::Int: 206 | case QMetaType::Float: 207 | case QMetaType::Double: 208 | { 209 | return array.at(id).toVariant(); 210 | } 211 | 212 | default: 213 | qWarning() << "Keyframe property type not handled:" << type; 214 | } 215 | 216 | return QVariant(); 217 | } 218 | 219 | bool QQuickKeyframeGroupPrivate::loadKeyframes(bool fromBinary) 220 | { 221 | Q_Q(QQuickKeyframeGroup); 222 | 223 | QCborStreamReader reader; 224 | QFile dataFile; 225 | if (!fromBinary) { 226 | // Resolve URL similar to QQuickImage source 227 | QUrl loadUrl = keyframeSource; 228 | QQmlContext *context = qmlContext(q); 229 | if (context) 230 | loadUrl = context->resolvedUrl(keyframeSource); 231 | QString dataFilePath = QQmlFile::urlToLocalFileOrQrc(loadUrl); 232 | 233 | dataFile.setFileName(dataFilePath); 234 | if (!dataFile.open(QIODevice::ReadOnly)) { 235 | // Invalid file 236 | qWarning() << "Unable to open keyframeSource:" << dataFilePath; 237 | return false; 238 | } 239 | reader.setDevice(&dataFile); 240 | } else { 241 | reader.addData(keyframeData); 242 | } 243 | 244 | auto error = [&reader](const QString &msg = QString()) { 245 | if (!msg.isEmpty()) 246 | qWarning() << "Corrupt keyframeSource" << msg; 247 | else 248 | qWarning() << "Corrupt keyframeSource" << reader.lastError().toString(); 249 | return false; 250 | }; 251 | 252 | QCborValue kfSrcCborValue = QCborValue::fromCbor(reader); 253 | if (reader.lastError() != QCborError::NoError || !kfSrcCborValue.isArray()) 254 | return error(QStringLiteral("invalid format.(array expected)")); 255 | 256 | QCborArray kfSrcCborArray = kfSrcCborValue.toArray(); 257 | // [ "QTimelineKeyframes", version(int), property type(int), [...]] 258 | if (kfSrcCborArray.size() != 4) 259 | return error(QStringLiteral("invalid data size")); 260 | 261 | if (kfSrcCborArray.at(0).toString() != QStringLiteral("QTimelineKeyframes")) 262 | return error(QStringLiteral("invalid keyframeSource header string")); 263 | 264 | if (kfSrcCborArray.at(1).toInteger() != 1) 265 | return error(QStringLiteral("invalid keyframeSource version %1").arg(kfSrcCborArray.at(1).toInteger())); 266 | 267 | // QMetaType::UnknownType = 0; 268 | QMetaType::Type propertyType = static_cast(kfSrcCborArray.at(2).toInteger(0)); 269 | const int frameSize = validFrameSize(propertyType); 270 | if (frameSize < 0) 271 | return error(QStringLiteral("unsupported property type")); 272 | 273 | // Start keyframes array 274 | QCborArray kfArray = kfSrcCborArray.at(3).toArray(); 275 | const auto arraySize = kfArray.size(); 276 | bool validKeyframeData = true; 277 | for (qsizetype i = 0; i < arraySize - frameSize; i += frameSize) { 278 | auto keyframe = std::make_unique(q); 279 | 280 | keyframe->setFrame(kfArray.at(i).toDouble()); 281 | 282 | QCborValue easingTypeValue = kfArray.at(i + 1); 283 | if (!easingTypeValue.isInteger()) { 284 | validKeyframeData = false; 285 | break; 286 | } 287 | keyframe->setEasing(static_cast(easingTypeValue.toInteger())); 288 | 289 | QVariant value = qQuickKeyframeReadProperty(kfArray, i + 2, propertyType); 290 | if (value.isValid()) { 291 | keyframe->setValue(value); 292 | keyframes.append(keyframe.release()); 293 | } else { 294 | validKeyframeData = false; 295 | break; 296 | } 297 | } 298 | 299 | if (!validKeyframeData) { 300 | qWarning() << "Invalid keyframe data"; 301 | resetKeyframes(); 302 | return false; 303 | } 304 | 305 | return true; 306 | } 307 | 308 | void QQuickKeyframeGroupPrivate::resetKeyframes() 309 | { 310 | qDeleteAll(keyframes); 311 | keyframes.clear(); 312 | } 313 | 314 | void QQuickKeyframeGroupPrivate::append_keyframe(QQmlListProperty *list, QQuickKeyframe *a) 315 | { 316 | auto q = static_cast(list->object); 317 | q->d_func()->keyframes.append(a); 318 | q->d_func()->setupKeyframes(); 319 | q->reset(); 320 | } 321 | 322 | qsizetype QQuickKeyframeGroupPrivate::keyframe_count(QQmlListProperty *list) 323 | { 324 | auto q = static_cast(list->object); 325 | return q->d_func()->keyframes.size(); 326 | } 327 | 328 | QQuickKeyframe* QQuickKeyframeGroupPrivate::keyframe_at(QQmlListProperty *list, qsizetype pos) 329 | { 330 | auto q = static_cast(list->object); 331 | return q->d_func()->keyframes.at(pos); 332 | } 333 | 334 | void QQuickKeyframeGroupPrivate::clear_keyframes(QQmlListProperty *list) 335 | { 336 | auto q = static_cast(list->object); 337 | q->d_func()->keyframes.clear(); 338 | q->d_func()->setupKeyframes(); 339 | } 340 | 341 | class QQuickKeyframePrivate : public QObjectPrivate 342 | { 343 | Q_DECLARE_PUBLIC(QQuickKeyframe) 344 | public: 345 | QQuickKeyframePrivate() = default; 346 | 347 | qreal frame = 0; 348 | QEasingCurve easingCurve; 349 | QVariant value; 350 | }; 351 | 352 | /*! 353 | \qmltype Keyframe 354 | \inherits QtObject 355 | \nativetype QQuickKeyframe 356 | \inqmlmodule QtQuick.Timeline 357 | \ingroup qtqmltypes 358 | 359 | \brief A keyframe. 360 | 361 | The value of a keyframe on a timeline. 362 | 363 | An easing curve can be attached to the keyframe. 364 | */ 365 | 366 | /*! 367 | \qmlproperty double Keyframe::frame 368 | 369 | The position of the keyframe on the timeline. 370 | */ 371 | 372 | /*! 373 | \qmlproperty var Keyframe::easing 374 | 375 | The easing curve attached to the keyframe. 376 | */ 377 | 378 | /*! 379 | \qmlproperty var Keyframe::value 380 | 381 | The value of the keyframe. 382 | */ 383 | 384 | /*! 385 | \qmlsignal Keyframe::easingCurveChanged 386 | 387 | This signal is emitted when the easing curve attached to the keyframe 388 | changes. 389 | */ 390 | 391 | QQuickKeyframe::QQuickKeyframe(QObject *parent) 392 | : QObject(*(new QQuickKeyframePrivate), parent) 393 | { 394 | } 395 | 396 | qreal QQuickKeyframe::frame() const 397 | { 398 | Q_D(const QQuickKeyframe); 399 | return d->frame; 400 | } 401 | 402 | void QQuickKeyframe::setFrame(qreal f) 403 | { 404 | Q_D(QQuickKeyframe); 405 | if (d->frame == f) 406 | return; 407 | d->frame = f; 408 | 409 | reset(); 410 | 411 | emit frameChanged(); 412 | } 413 | 414 | void QQuickKeyframe::reset() 415 | { 416 | auto keyframes = qobject_cast(parent()); 417 | if (keyframes) 418 | keyframes->reset(); 419 | } 420 | 421 | QQuickKeyframe::QQuickKeyframe(QQuickKeyframePrivate &dd, QObject *parent) 422 | : QObject(dd, parent) 423 | { 424 | } 425 | 426 | /*! 427 | \qmltype KeyframeGroup 428 | \inherits QtObject 429 | \nativetype QQuickKeyframeGroup 430 | \inqmlmodule QtQuick.Timeline 431 | \ingroup qtqmltypes 432 | 433 | \brief A keyframe group. 434 | 435 | A keyframe group contains all keyframes for a specific property of an item 436 | and always belongs to a timeline. 437 | */ 438 | 439 | /*! 440 | \qmlproperty var KeyframeGroup::target 441 | 442 | The item that is targeted by the keyframe group. 443 | */ 444 | 445 | /*! 446 | \qmlproperty string KeyframeGroup::property 447 | 448 | The property that is targeted by the keyframe group. 449 | */ 450 | 451 | /*! 452 | \qmlproperty list KeyframeGroup::keyframes 453 | \readonly 454 | 455 | A list of keyframes that belong to the keyframe group. 456 | */ 457 | 458 | /*! 459 | \qmlproperty url KeyframeGroup::keyframeSource 460 | 461 | The URL to a file containing binary keyframe data. 462 | 463 | \note KeyframeGroup should either set this property or contain 464 | Keyframe child elements. Using both can lead to an undefined behavior. 465 | 466 | \since 1.1 467 | */ 468 | 469 | QQuickKeyframeGroup::QQuickKeyframeGroup(QObject *parent) 470 | : QObject(*(new QQuickKeyframeGroupPrivate), parent) 471 | { 472 | 473 | } 474 | 475 | QQmlListProperty QQuickKeyframeGroup::keyframes() 476 | { 477 | Q_D(QQuickKeyframeGroup); 478 | 479 | return { this, &d->keyframes, QQuickKeyframeGroupPrivate::append_keyframe, 480 | QQuickKeyframeGroupPrivate::keyframe_count, 481 | QQuickKeyframeGroupPrivate::keyframe_at, 482 | QQuickKeyframeGroupPrivate::clear_keyframes }; 483 | } 484 | 485 | QObject *QQuickKeyframeGroup::target() const 486 | { 487 | Q_D(const QQuickKeyframeGroup); 488 | return d->target; 489 | } 490 | 491 | void QQuickKeyframeGroup::setTargetObject(QObject *o) 492 | { 493 | Q_D(QQuickKeyframeGroup); 494 | if (d->target == o) 495 | return; 496 | d->target = o; 497 | 498 | if (!property().isEmpty()) 499 | init(); 500 | 501 | emit targetChanged(); 502 | } 503 | 504 | QString QQuickKeyframeGroup::property() const 505 | { 506 | Q_D(const QQuickKeyframeGroup); 507 | return d->propertyName; 508 | } 509 | 510 | void QQuickKeyframeGroup::setProperty(const QString &n) 511 | { 512 | Q_D(QQuickKeyframeGroup); 513 | if (d->propertyName == n) 514 | return; 515 | d->propertyName = n; 516 | 517 | if (target()) 518 | init(); 519 | 520 | emit propertyChanged(); 521 | } 522 | 523 | QUrl QQuickKeyframeGroup::keyframeSource() const 524 | { 525 | Q_D(const QQuickKeyframeGroup); 526 | return d->keyframeSource; 527 | } 528 | 529 | void QQuickKeyframeGroup::setKeyframeSource(const QUrl &source) 530 | { 531 | Q_D(QQuickKeyframeGroup); 532 | if (d->keyframeSource == source) 533 | return; 534 | 535 | if (d->keyframes.size() > 0) { 536 | // Remove possible previously loaded keyframes 537 | d->resetKeyframes(); 538 | d->keyframeData.clear(); 539 | } 540 | 541 | d->keyframeSource = source; 542 | if (d->loadKeyframes()) 543 | d->setupKeyframes(); 544 | reset(); 545 | 546 | emit keyframeSourceChanged(); 547 | } 548 | 549 | const QByteArray QQuickKeyframeGroup::keyframeData() const 550 | { 551 | Q_D(const QQuickKeyframeGroup); 552 | return d->keyframeData; 553 | } 554 | 555 | void QQuickKeyframeGroup::setKeyframeData(const QByteArray &data) 556 | { 557 | Q_D(QQuickKeyframeGroup); 558 | if (d->keyframeData == data) 559 | return; 560 | 561 | if (d->keyframes.size() > 0) { 562 | // Remove possible previously loaded keyframes 563 | d->resetKeyframes(); 564 | d->keyframeSource.clear(); 565 | } 566 | 567 | d->keyframeData = data; 568 | if (d->loadKeyframes(true)) 569 | d->setupKeyframes(); 570 | reset(); 571 | 572 | emit keyframeSourceChanged(); 573 | } 574 | 575 | QVariant QQuickKeyframeGroup::evaluate(qreal frame) const 576 | { 577 | Q_D(const QQuickKeyframeGroup); 578 | 579 | if (d->sortedKeyframes.isEmpty()) 580 | return QVariant(); 581 | 582 | static QQuickKeyframe dummy; 583 | auto timeline = qobject_cast(parent()); 584 | if (timeline) 585 | dummy.setFrame(timeline->startFrame() - 0.0001); 586 | dummy.setValue(d->originalValue); 587 | 588 | QQuickKeyframe *lastFrame = &dummy; 589 | 590 | for (auto keyFrame : std::as_const(d->sortedKeyframes)) { 591 | if (qFuzzyCompare(frame, keyFrame->frame()) || frame < keyFrame->frame()) 592 | return keyFrame->evaluate(lastFrame, frame, d->userType); 593 | lastFrame = keyFrame; 594 | } 595 | 596 | return lastFrame->value(); 597 | } 598 | 599 | void QQuickKeyframeGroup::setProperty(qreal frame) 600 | { 601 | Q_D(QQuickKeyframeGroup); 602 | 603 | if (target()) { 604 | QQmlProperty qmlProperty(target(), property()); 605 | 606 | d->lastValue = evaluate(frame); 607 | 608 | if (!qmlProperty.write(d->lastValue)) 609 | qWarning() << "Cannot set property" << property(); 610 | } 611 | } 612 | 613 | void QQuickKeyframeGroup::init() 614 | { 615 | Q_D(QQuickKeyframeGroup); 616 | if (target()) { 617 | QQmlProperty qmlProperty(target(), property()); 618 | d->originalValue = QQmlProperty::read(target(), property()); 619 | d->userType = qmlProperty.property().userType(); 620 | if (d->originalBinding) 621 | d->originalBinding = nullptr; 622 | d->originalBinding = QQmlAnyBinding::ofProperty(qmlProperty); 623 | if (property().contains(QLatin1Char('.'))) { 624 | if (d->userType == QMetaType::QVector2D 625 | || d->userType == QMetaType::QVector3D 626 | || d->userType == QMetaType::QVector4D 627 | || d->userType == QMetaType::QQuaternion) 628 | d->userType = QMetaType::Double; 629 | } 630 | } 631 | } 632 | 633 | void QQuickKeyframeGroup::resetDefaultValue() 634 | { 635 | Q_D(QQuickKeyframeGroup); 636 | 637 | if (QQmlProperty::read(target(), property()) == d->lastValue) { 638 | if (d->originalBinding) { 639 | QQmlProperty qmlProperty(target(), property()); 640 | d->originalBinding.installOn(qmlProperty); 641 | d->originalBinding = nullptr; 642 | } else { 643 | QQmlProperty::write(target(), property(), d->originalValue); 644 | } 645 | } 646 | } 647 | 648 | void QQuickKeyframeGroup::reset() 649 | { 650 | Q_D(QQuickKeyframeGroup); 651 | if (!d->componentComplete) 652 | return; 653 | 654 | auto *timeline = qobject_cast(parent()); 655 | if (timeline) 656 | timeline->reevaluate(); 657 | } 658 | 659 | void QQuickKeyframeGroup::setupKeyframes() 660 | { 661 | Q_D(QQuickKeyframeGroup); 662 | 663 | if (d->componentComplete) 664 | d->setupKeyframes(); 665 | } 666 | 667 | void QQuickKeyframeGroup::classBegin() 668 | { 669 | Q_D(QQuickKeyframeGroup); 670 | d->componentComplete = false; 671 | } 672 | 673 | void QQuickKeyframeGroup::componentComplete() 674 | { 675 | Q_D(QQuickKeyframeGroup); 676 | d->componentComplete = true; 677 | setupKeyframes(); 678 | } 679 | 680 | QEasingCurve QQuickKeyframe::easing() const 681 | { 682 | Q_D(const QQuickKeyframe); 683 | return d->easingCurve; 684 | } 685 | 686 | void QQuickKeyframe::setEasing(const QEasingCurve &e) 687 | { 688 | Q_D(QQuickKeyframe); 689 | if (d->easingCurve == e) 690 | return; 691 | 692 | d->easingCurve = e; 693 | 694 | reset(); 695 | 696 | emit easingCurveChanged(); 697 | } 698 | 699 | QVariant QQuickKeyframe::value() const 700 | { 701 | Q_D(const QQuickKeyframe); 702 | return d->value; 703 | } 704 | 705 | void QQuickKeyframe::setValue(const QVariant &v) 706 | { 707 | Q_D(QQuickKeyframe); 708 | if (d->value == v) 709 | return; 710 | d->value = v; 711 | 712 | reset(); 713 | 714 | emit valueChanged(); 715 | } 716 | 717 | QVariant QQuickKeyframe::evaluate(QQuickKeyframe *pre, qreal frametime, int userType) const 718 | { 719 | QVariantAnimation::Interpolator interpolator = QVariantAnimationPrivate::getInterpolator(userType); 720 | if (!pre) 721 | return value(); 722 | 723 | QVariant preValue = pre->value(); 724 | qreal preFrame = pre->frame(); 725 | 726 | qreal duration = frame() - preFrame; 727 | qreal offset = frametime - preFrame; 728 | 729 | qreal progress = easing().valueForProgress(offset / duration); 730 | 731 | const QMetaType targetType(userType); 732 | preValue.convert(targetType); 733 | QVariant convertedValue = value(); 734 | convertedValue.convert(targetType); 735 | 736 | if (!interpolator) { 737 | if (progress < 1.0) 738 | return preValue; 739 | 740 | return convertedValue; 741 | } 742 | 743 | if (preValue.isValid() && convertedValue.isValid()) 744 | return interpolator(preValue.constData(), convertedValue.constData(), progress); 745 | 746 | qWarning() << "invalid keyframe target" << preValue << convertedValue << userType; 747 | 748 | return QVariant(); 749 | } 750 | 751 | QT_END_NAMESPACE 752 | 753 | 754 | --------------------------------------------------------------------------------