From 932819fe0a1bf8ce41587c9dadcf9a647956c7b6 Mon Sep 17 00:00:00 2001 From: Andre Hartmann Date: Mon, 5 Aug 2019 20:45:35 +0200 Subject: [PATCH 01/10] QtOutputFormatter: Add alternate QTest failure pattern The QTest failure output was modeled after the compiler error pattern, which contains file name and line number to point to the precise error position. Unfortunately, some compilers (MSVC) separate the line number from the file name with "(line)", while others use ":line". Let's handle both when parsing the application or test output, as it was before 9a0ef8ac64, which explicitly separated both parts in the regexp pattern. This amends commit 9a0ef8ac64eb47fd932aebbb0751c431c0b74a08 Fixes: QTCREATORBUG-22800 Change-Id: I58154111612bbb452af19b34f7217afc6dd098df Reviewed-by: Christian Stenger Reviewed-by: Christian Kandeler Reviewed-by: Samuel Gaist --- src/plugins/qtsupport/qtoutputformatter.cpp | 10 ++++++++++ src/plugins/qtsupport/qtoutputformatter.h | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/plugins/qtsupport/qtoutputformatter.cpp b/src/plugins/qtsupport/qtoutputformatter.cpp index e6e6581928b..dc90c8fda32 100644 --- a/src/plugins/qtsupport/qtoutputformatter.cpp +++ b/src/plugins/qtsupport/qtoutputformatter.cpp @@ -420,6 +420,11 @@ void QtSupportPlugin::testQtOutputFormatter_data() << 9 << 37 << "../TestProject/test.cpp(123)" << "../TestProject/test.cpp" << 123 << -1; + QTest::newRow("Unix failed QTest link (alternate)") + << " Loc: [/Projects/TestProject/test.cpp:123]" + << 9 << 43 << "/Projects/TestProject/test.cpp:123" + << "/Projects/TestProject/test.cpp" << 123 << -1; + QTest::newRow("Unix relative file link") << "file://../main.cpp:157" << 0 << 22 << "file://../main.cpp:157" @@ -431,6 +436,11 @@ void QtSupportPlugin::testQtOutputFormatter_data() << 0 << 28 << "..\\TestProject\\test.cpp(123)" << "../TestProject/test.cpp" << 123 << -1; + QTest::newRow("Windows failed QTest link (alternate)") + << " Loc: [c:\\Projects\\TestProject\\test.cpp:123]" + << 9 << 45 << "c:\\Projects\\TestProject\\test.cpp:123" + << "c:/Projects/TestProject/test.cpp" << 123 << -1; + QTest::newRow("Windows failed QTest link with carriage return") << "..\\TestProject\\test.cpp(123) : failure location\r" << 0 << 28 << "..\\TestProject\\test.cpp(123)" diff --git a/src/plugins/qtsupport/qtoutputformatter.h b/src/plugins/qtsupport/qtoutputformatter.h index 45f1a1e3966..1d5a886b199 100644 --- a/src/plugins/qtsupport/qtoutputformatter.h +++ b/src/plugins/qtsupport/qtoutputformatter.h @@ -33,7 +33,7 @@ #define QT_QML_URL_REGEXP "(?:file|qrc):(?://)?/.+?" #define QT_ASSERT_REGEXP "ASSERT: .* in file (.+, line \\d+)" #define QT_ASSERT_X_REGEXP "ASSERT failure in .*: \".*\", file (.+, line \\d+)" -#define QT_TEST_FAIL_UNIX_REGEXP "^ Loc: \\[((?.+)\\((?\\d+)\\))\\]$" +#define QT_TEST_FAIL_UNIX_REGEXP "^ Loc: \\[((?.+)(?|\\((?\\d+)\\)|:(?\\d+)))\\]$" #define QT_TEST_FAIL_WIN_REGEXP "^((?.+)\\((?\\d+)\\)) : failure location\\s*$" namespace ProjectExplorer { class Project; } From db2120c451a642256e5ffddfa604d9c6904f5fb3 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 7 Aug 2019 15:27:34 +0200 Subject: [PATCH 02/10] QmlDesigner: Fix empty lines in annoation block The annotation block on the bottom aggregated empty lines. Task-number: QDS-377 Change-Id: I29806ce224717348b50258850bdc4a7bc6140eb8 Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/designercore/model/rewriterview.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp index bbd5fad60aa..1cfec9fd04e 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp @@ -1008,6 +1008,9 @@ void RewriterView::writeAuxiliaryData() if (startIndex > 0 && endIndex > 0) newText.remove(startIndex, endIndex - startIndex + annotationsEnd().length()); + newText = newText.trimmed(); + newText.append("\n"); + QString auxData = auxiliaryDataAsQML(); if (!auxData.isEmpty()) { From 6ab4d694403cec64c5aa106bb4d457e478e1b8fa Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Wed, 7 Aug 2019 14:57:20 +0200 Subject: [PATCH 03/10] QmlDesigner: Fix TextField context menu closing Change-Id: I596e5cee2e3c058b1fe5b0f0f1f976a60784d88b Reviewed-by: Thomas Hartmann --- .../imports/StudioControls/TextField.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TextField.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TextField.qml index 7c14e833b96..5faa7cbd128 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TextField.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/TextField.qml @@ -94,6 +94,11 @@ T.TextField { myTextEdit: myTextField } + onEditChanged: { + if (myTextField.edit) + contextMenu.close() + } + ActionIndicator { id: actionIndicator myControl: myTextField From c9b91e7e6b25d5c76bd736c054c681a6877f625c Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Wed, 7 Aug 2019 16:00:21 +0200 Subject: [PATCH 04/10] QmlDesigner: reduce enumeration to header only It is used in some other external qmldesigner plugin and these are built against current dev packages, which are not contain this cpp file. Different solution would be to add: r"^share/qtcreator/qml/qmlpuppet/types/enumeration.cpp$", to scripts/createDevPackage.py which feels not that clean. Change-Id: Ia1fb5c02f457d98474218689ebf6483706265dde Reviewed-by: Thomas Hartmann --- .../qml2puppet/instances/objectnodeinstance.h | 2 +- .../qml/qmlpuppet/types/enumeration.cpp | 116 ------------------ .../qml/qmlpuppet/types/enumeration.h | 78 +++++++++--- share/qtcreator/qml/qmlpuppet/types/types.pri | 2 - .../designercore/include/variantproperty.h | 3 +- .../designercore/model/propertyparser.cpp | 3 +- .../designercore/model/texttomodelmerger.cpp | 3 +- src/plugins/qmldesigner/qmldesignerplugin.qbs | 1 - src/tools/qml2puppet/CMakeLists.txt | 2 +- src/tools/qml2puppet/qml2puppet.qbs | 1 - 10 files changed, 72 insertions(+), 139 deletions(-) delete mode 100644 share/qtcreator/qml/qmlpuppet/types/enumeration.cpp diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h index 07c5d50b6e5..55114e872a6 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h @@ -34,7 +34,7 @@ #include #include -#include "enumeration.h" +#include QT_BEGIN_NAMESPACE class QQmlContext; diff --git a/share/qtcreator/qml/qmlpuppet/types/enumeration.cpp b/share/qtcreator/qml/qmlpuppet/types/enumeration.cpp deleted file mode 100644 index 12435838ea0..00000000000 --- a/share/qtcreator/qml/qmlpuppet/types/enumeration.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ - -#include "enumeration.h" - -#include -#include -#include - -namespace QmlDesigner { - -Enumeration::Enumeration() = default; - -Enumeration::Enumeration(const EnumerationName &enumerationName) - : m_enumerationName(enumerationName) -{ -} - -Enumeration::Enumeration(const QString &enumerationName) - : m_enumerationName(enumerationName.toUtf8()) -{ -} - -Enumeration::Enumeration(const QString &scope, const QString &name) -{ - QString enumerationString = scope + QLatin1Char('.') + name; - - m_enumerationName = enumerationString.toUtf8(); -} - -QmlDesigner::EnumerationName QmlDesigner::Enumeration::scope() const -{ - return m_enumerationName.split('.').constFirst(); -} - -EnumerationName Enumeration::name() const -{ - return m_enumerationName.split('.').last(); -} - -EnumerationName Enumeration::toEnumerationName() const -{ - return m_enumerationName; -} - -QString Enumeration::toString() const -{ - return QString::fromUtf8(m_enumerationName); -} - -QString Enumeration::nameToString() const -{ - return QString::fromUtf8(name()); -} - -QDataStream &operator<<(QDataStream &out, const Enumeration &enumeration) -{ - out << enumeration.toEnumerationName(); - - return out; -} - -QDataStream &operator>>(QDataStream &in, Enumeration &enumeration) -{ - in >> enumeration.m_enumerationName; - - return in; -} - - -bool operator ==(const Enumeration &first, const Enumeration &second) -{ - return first.m_enumerationName == second.m_enumerationName; -} - -bool operator <(const Enumeration &first, const Enumeration &second) -{ - return first.m_enumerationName < second.m_enumerationName; -} - -QDebug operator <<(QDebug debug, const Enumeration &enumeration) -{ - debug.nospace() << "Enumeration(" - << enumeration.toString() - << ")"; - - return debug; -} - - -} // namespace QmlDesigner - - - diff --git a/share/qtcreator/qml/qmlpuppet/types/enumeration.h b/share/qtcreator/qml/qmlpuppet/types/enumeration.h index d7bc3ac049d..c764f16cb2e 100644 --- a/share/qtcreator/qml/qmlpuppet/types/enumeration.h +++ b/share/qtcreator/qml/qmlpuppet/types/enumeration.h @@ -30,6 +30,10 @@ #include #include +#include +#include +#include + namespace QmlDesigner { using EnumerationName = QByteArray; @@ -41,29 +45,75 @@ class Enumeration friend QDataStream &operator>>(QDataStream &in, Enumeration &enumeration); public: - Enumeration(); - Enumeration(const EnumerationName &enumerationName); - Enumeration(const QString &enumerationName); - Enumeration(const QString &scope, const QString &name); + Enumeration() = default; + Enumeration(const EnumerationName &enumerationName) + : m_enumerationName(enumerationName) + { + } + Enumeration(const QString &enumerationName) + : m_enumerationName(enumerationName.toUtf8()) + { + } + Enumeration(const QString &scope, const QString &name) + { + QString enumerationString = scope + QLatin1Char('.') + name; + m_enumerationName = enumerationString.toUtf8(); + } - EnumerationName scope() const; - EnumerationName name() const; - EnumerationName toEnumerationName() const; - QString toString() const; - QString nameToString() const; + EnumerationName scope() const + { + return m_enumerationName.split('.').constFirst(); + } + EnumerationName name() const + { + return m_enumerationName.split('.').last(); + } + EnumerationName toEnumerationName() const + { + return m_enumerationName; + } + QString toString() const + { + return QString::fromUtf8(m_enumerationName); + } + QString nameToString() const + { + return QString::fromUtf8(name()); + } private: EnumerationName m_enumerationName; }; -QDataStream &operator<<(QDataStream &out, const Enumeration &enumeration); -QDataStream &operator>>(QDataStream &in, Enumeration &enumeration); +inline QDataStream &operator<<(QDataStream &out, const Enumeration &enumeration){ + out << enumeration.toEnumerationName(); + return out; +} -bool operator ==(const Enumeration &first, const Enumeration &second); -bool operator <(const Enumeration &first, const Enumeration &second); +inline QDataStream &operator>>(QDataStream &in, Enumeration &enumeration) +{ + in >> enumeration.m_enumerationName; + return in; +} -QDebug operator <<(QDebug debug, const Enumeration &enumeration); +inline bool operator==(const Enumeration &first, const Enumeration &second) +{ + return first.m_enumerationName == second.m_enumerationName; +} + +inline bool operator<(const Enumeration &first, const Enumeration &second) +{ + return first.m_enumerationName < second.m_enumerationName; +} + +inline QDebug operator <<(QDebug debug, const Enumeration &enumeration) +{ + debug.nospace() << "Enumeration(" + << enumeration.toString() + << ")"; + return debug; +} } // namespace QmlDesigner diff --git a/share/qtcreator/qml/qmlpuppet/types/types.pri b/share/qtcreator/qml/qmlpuppet/types/types.pri index 87425205e6d..e5f10bcbe46 100644 --- a/share/qtcreator/qml/qmlpuppet/types/types.pri +++ b/share/qtcreator/qml/qmlpuppet/types/types.pri @@ -1,5 +1,3 @@ INCLUDEPATH += $$PWD/ HEADERS += $$PWD/enumeration.h - -SOURCES += $$PWD/enumeration.cpp diff --git a/src/plugins/qmldesigner/designercore/include/variantproperty.h b/src/plugins/qmldesigner/designercore/include/variantproperty.h index c909958de04..8f25ab3109a 100644 --- a/src/plugins/qmldesigner/designercore/include/variantproperty.h +++ b/src/plugins/qmldesigner/designercore/include/variantproperty.h @@ -27,7 +27,8 @@ #include "qmldesignercorelib_global.h" #include "abstractproperty.h" -#include "enumeration.h" + +#include QT_BEGIN_NAMESPACE class QTextStream; diff --git a/src/plugins/qmldesigner/designercore/model/propertyparser.cpp b/src/plugins/qmldesigner/designercore/model/propertyparser.cpp index c6d19b801ab..b4af8564675 100644 --- a/src/plugins/qmldesigner/designercore/model/propertyparser.cpp +++ b/src/plugins/qmldesigner/designercore/model/propertyparser.cpp @@ -24,7 +24,8 @@ ****************************************************************************/ #include "propertyparser.h" -#include "enumeration.h" + +#include #include #include diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index ea72cc51a20..45c07d20afb 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -29,7 +29,6 @@ #include "modelnodepositionstorage.h" #include "abstractproperty.h" #include "bindingproperty.h" -#include "enumeration.h" #include "filemanager/firstdefinitionfinder.h" #include "filemanager/objectlengthcalculator.h" #include "filemanager/qmlrefactoring.h" @@ -42,6 +41,8 @@ #include "rewriterview.h" #include "variantproperty.h" +#include + #include #include #include diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 4cba901e20f..2db56446f3c 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -192,7 +192,6 @@ Project { "interfaces/nodeinstanceclientinterface.h", "interfaces/nodeinstanceserverinterface.cpp", "interfaces/nodeinstanceserverinterface.h", - "types/enumeration.cpp", "types/enumeration.h", ] } diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index a5b24d59de1..3715ae9349e 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -126,7 +126,7 @@ extend_qtc_executable(qml2puppet extend_qtc_executable(qml2puppet SOURCES_PREFIX "${SRCDIR}/types" SOURCES - enumeration.cpp enumeration.h + enumeration.h ) extend_qtc_executable(qml2puppet diff --git a/src/tools/qml2puppet/qml2puppet.qbs b/src/tools/qml2puppet/qml2puppet.qbs index 25c3f393098..02a6f6e31f2 100644 --- a/src/tools/qml2puppet/qml2puppet.qbs +++ b/src/tools/qml2puppet/qml2puppet.qbs @@ -121,7 +121,6 @@ QtcTool { "interfaces/nodeinstanceserverinterface.cpp", "interfaces/nodeinstanceserverinterface.h", "qmlprivategate/qmlprivategate.h", - "types/enumeration.cpp", "types/enumeration.h", ] } From a96095a2bfe8f9b6103c37d5f4bcc06c06970292 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Thu, 8 Aug 2019 08:26:56 +0200 Subject: [PATCH 05/10] CMake build: Adapt to removed file Fix-up of c9b91e7e6b25d5c76bd736c054c681a6877f625c Change-Id: I6b2e4a03f2834020d76d3534cdb7cfbb82c77b8c Reviewed-by: Tim Jenssen --- src/plugins/qmldesigner/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 2a26bb8e7bd..8801aed4a9a 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -149,7 +149,7 @@ extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner SOURCES_PREFIX ../../../share/qtcreator/qml/qmlpuppet/types - SOURCES enumeration.cpp enumeration.h + SOURCES enumeration.h ) extend_qtc_plugin(QmlDesigner From 4a673896a697975e16a1531fc22895dc3dbee0a7 Mon Sep 17 00:00:00 2001 From: Knud Dollereder Date: Fri, 28 Jun 2019 10:45:28 +0200 Subject: [PATCH 06/10] Connect CurveEditor edits to the timeline module Change-Id: Ic00e0840da34bdbb8627b2fe2d8546a867b24966 Reviewed-by: Thomas Hartmann --- .../components/curveeditor/animationcurve.cpp | 233 ++++++-- .../components/curveeditor/animationcurve.h | 30 +- .../components/curveeditor/curveeditor.cpp | 64 ++- .../components/curveeditor/curveeditor.h | 5 + .../components/curveeditor/curveeditor.pri | 4 +- .../components/curveeditor/curveeditorstyle.h | 4 + .../components/curveeditor/curvesegment.cpp | 500 ++++++++++++++++++ .../curveeditor/{detail => }/curvesegment.h | 27 + .../curveeditor/detail/curveitem.cpp | 247 +++++++-- .../components/curveeditor/detail/curveitem.h | 39 +- .../curveeditor/detail/curvesegment.cpp | 277 ---------- .../curveeditor/detail/graphicsscene.cpp | 6 +- .../curveeditor/detail/graphicsview.cpp | 50 +- .../curveeditor/detail/graphicsview.h | 6 + .../curveeditor/detail/handleitem.cpp | 21 + .../curveeditor/detail/handleitem.h | 3 + .../curveeditor/detail/keyframeitem.cpp | 152 ++++-- .../curveeditor/detail/keyframeitem.h | 16 + .../curveeditor/detail/selectableitem.cpp | 2 - .../curveeditor/detail/selector.cpp | 4 +- .../curveeditor/detail/treemodel.cpp | 2 +- .../curveeditor/detail/treeview.cpp | 27 +- .../components/curveeditor/detail/treeview.h | 2 + .../components/curveeditor/detail/utils.cpp | 10 - .../components/curveeditor/detail/utils.h | 34 +- .../components/curveeditor/keyframe.cpp | 73 ++- .../components/curveeditor/keyframe.h | 30 ++ .../components/curveeditor/treeitem.cpp | 45 +- .../components/curveeditor/treeitem.h | 28 +- .../timelineeditor/animationcurvedialog.cpp | 66 +++ .../timelineeditor/animationcurvedialog.h | 52 ++ .../animationcurveeditormodel.cpp | 210 ++++++++ .../animationcurveeditormodel.h | 72 +++ .../timelineeditor/timelineconstants.h | 1 + .../timelineeditor/timelineeditor.pri | 8 +- .../timelineeditor/timelinetoolbar.cpp | 50 +- .../timelineeditor/timelinetoolbar.h | 15 +- .../timelineeditor/timelineview.cpp | 98 ++-- .../components/timelineeditor/timelineview.h | 4 + .../timelineeditor/timelinewidget.cpp | 83 ++- .../timelineeditor/timelinewidget.h | 3 + .../include/qmltimelinekeyframegroup.h | 11 +- .../model/qmltimelinekeyframegroup.cpp | 62 ++- 43 files changed, 2145 insertions(+), 531 deletions(-) create mode 100644 src/plugins/qmldesigner/components/curveeditor/curvesegment.cpp rename src/plugins/qmldesigner/components/curveeditor/{detail => }/curvesegment.h (75%) delete mode 100644 src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.cpp create mode 100644 src/plugins/qmldesigner/components/timelineeditor/animationcurvedialog.cpp create mode 100644 src/plugins/qmldesigner/components/timelineeditor/animationcurvedialog.h create mode 100644 src/plugins/qmldesigner/components/timelineeditor/animationcurveeditormodel.cpp create mode 100644 src/plugins/qmldesigner/components/timelineeditor/animationcurveeditormodel.h diff --git a/src/plugins/qmldesigner/components/curveeditor/animationcurve.cpp b/src/plugins/qmldesigner/components/curveeditor/animationcurve.cpp index 8e2d5224e10..f329ac6bae4 100644 --- a/src/plugins/qmldesigner/components/curveeditor/animationcurve.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/animationcurve.cpp @@ -24,40 +24,69 @@ ****************************************************************************/ #include "animationcurve.h" -#include "detail/curvesegment.h" +#include "curvesegment.h" +#include "detail/utils.h" +#include #include +#include namespace DesignTools { AnimationCurve::AnimationCurve() - : m_frames() + : m_fromData(false) + , m_minY(std::numeric_limits::max()) + , m_maxY(std::numeric_limits::lowest()) + , m_frames() {} AnimationCurve::AnimationCurve(const std::vector &frames) - : m_frames(frames) + : m_fromData(false) , m_minY(std::numeric_limits::max()) , m_maxY(std::numeric_limits::lowest()) + , m_frames(frames) { - if (isValid()) { + analyze(); +} - for (auto e : extrema()) { +AnimationCurve::AnimationCurve(const QEasingCurve &easing, const QPointF &start, const QPointF &end) + : m_fromData(true) + , m_minY(std::numeric_limits::max()) + , m_maxY(std::numeric_limits::lowest()) + , m_frames() +{ + auto mapPosition = [start, end](const QPointF &pos) { + QPointF slope(end.x() - start.x(), end.y() - start.y()); + return QPointF(start.x() + slope.x() * pos.x(), start.y() + slope.y() * pos.y()); + }; - if (m_minY > e.y()) - m_minY = e.y(); + QVector points = easing.toCubicSpline(); + int numSegments = points.count() / 3; - if (m_maxY < e.y()) - m_maxY = e.y(); - } + Keyframe current; + Keyframe tmp(start); - for (auto &frame : qAsConst(m_frames)) { - if (frame.position().y() < m_minY) - m_minY = frame.position().y(); + current.setInterpolation(Keyframe::Interpolation::Bezier); + tmp.setInterpolation(Keyframe::Interpolation::Bezier); - if (frame.position().y() > m_maxY) - m_maxY = frame.position().y(); - } + for (int i = 0; i < numSegments; i++) { + QPointF p1 = mapPosition(points.at(i * 3)); + QPointF p2 = mapPosition(points.at(i * 3 + 1)); + QPointF p3 = mapPosition(points.at(i * 3 + 2)); + + current.setPosition(tmp.position()); + current.setLeftHandle(tmp.leftHandle()); + current.setRightHandle(p1); + + m_frames.push_back(current); + + tmp.setLeftHandle(p2); + tmp.setPosition(p3); } + + m_frames.push_back(tmp); + + analyze(); } bool AnimationCurve::isValid() const @@ -65,6 +94,11 @@ bool AnimationCurve::isValid() const return m_frames.size() >= 2; } +bool AnimationCurve::isFromData() const +{ + return m_fromData; +} + double AnimationCurve::minimumTime() const { if (!m_frames.empty()) @@ -91,6 +125,70 @@ double AnimationCurve::maximumValue() const return m_maxY; } +CurveSegment AnimationCurve::segment(double time) const +{ + CurveSegment seg; + for (auto &frame : m_frames) { + if (frame.position().x() > time) { + seg.setRight(frame); + return seg; + } + seg.setLeft(frame); + } + return CurveSegment(); +} + +std::vector AnimationCurve::segments() const +{ + if (m_frames.empty()) + return {}; + + std::vector out; + + CurveSegment current; + current.setLeft(m_frames.at(0)); + + for (size_t i = 1; i < m_frames.size(); ++i) { + current.setRight(m_frames[i]); + out.push_back(current); + current.setLeft(m_frames[i]); + } + return out; +} + +QPointF mapEasing(const QPointF &start, const QPointF &end, const QPointF &pos) +{ + QPointF slope(end.x() - start.x(), end.y() - start.y()); + return QPointF(start.x() + slope.x() * pos.x(), start.y() + slope.y() * pos.y()); +} + +QPainterPath AnimationCurve::simplePath() const +{ + if (m_frames.empty()) + return QPainterPath(); + + QPainterPath path(m_frames.front().position()); + + CurveSegment segment; + segment.setLeft(m_frames.front()); + + for (size_t i = 1; i < m_frames.size(); ++i) { + segment.setRight(m_frames[i]); + segment.extend(path); + segment.setLeft(m_frames[i]); + } + + return path; +} + +QPainterPath AnimationCurve::intersectionPath() const +{ + QPainterPath path = simplePath(); + QPainterPath reversed = path.toReversed(); + path.connectPath(reversed); + return path; +} + std::vector AnimationCurve::keyframes() const { return m_frames; @@ -100,19 +198,10 @@ std::vector AnimationCurve::extrema() const { std::vector out; - CurveSegment segment; - segment.setLeft(m_frames.at(0)); - - for (size_t i = 1; i < m_frames.size(); ++i) { - - segment.setRight(m_frames[i]); - + for (auto &&segment : segments()) { const auto es = segment.extrema(); out.insert(std::end(out), std::begin(es), std::end(es)); - - segment.setLeft(m_frames[i]); } - return out; } @@ -141,7 +230,7 @@ std::vector AnimationCurve::xForY(double y, uint segment) const return std::vector(); } -bool AnimationCurve::intersects(const QPointF &coord, double radius) +bool AnimationCurve::intersects(const QPointF &coord, double radiusX, double radiusY) const { if (m_frames.size() < 2) return false; @@ -152,36 +241,94 @@ bool AnimationCurve::intersects(const QPointF &coord, double radius) current.setLeft(m_frames.at(0)); for (size_t i = 1; i < m_frames.size(); ++i) { - Keyframe &frame = m_frames.at(i); + const Keyframe &frame = m_frames.at(i); current.setRight(frame); - if (current.containsX(coord.x() - radius) || - current.containsX(coord.x()) || - current.containsX(coord.x() + radius)) { + if (current.containsX(coord.x() - radiusX) || current.containsX(coord.x()) + || current.containsX(coord.x() + radiusX)) { influencer.push_back(current); } - if (frame.position().x() > coord.x() + radius) + if (frame.position().x() > coord.x() + radiusX) break; current.setLeft(frame); } for (auto &segment : influencer) { - for (auto &y : segment.yForX(coord.x())) { - QLineF line(coord.x(), y, coord.x(), coord.y()); - if (line.length() < radius) - return true; - } - - for (auto &x : segment.xForY(coord.y())) { - QLineF line(x, coord.y(), coord.x(), coord.y()); - if (line.length() < radius) - return true; - } + if (segment.intersects(coord, radiusX, radiusY)) + return true; } return false; } +void AnimationCurve::append(const AnimationCurve &other) +{ + if (!other.isValid()) + return; + + if (!isValid()) { + m_frames = other.keyframes(); + analyze(); + return; + } + + std::vector otherFrames = other.keyframes(); + m_frames.back().setRightHandle(otherFrames.front().rightHandle()); + m_frames.insert(std::end(m_frames), std::begin(otherFrames) + 1, std::end(otherFrames)); + analyze(); +} + +void AnimationCurve::insert(double time) +{ + CurveSegment seg = segment(time); + + if (!seg.isValid()) + return; + + auto insertFrames = [this](std::array &&frames) { + auto samePosition = [frames](const Keyframe &frame) { + return frame.position() == frames[0].position(); + }; + + auto iter = std::find_if(m_frames.begin(), m_frames.end(), samePosition); + if (iter != m_frames.end()) { + auto erased = m_frames.erase(iter, iter + 2); + m_frames.insert(erased, frames.begin(), frames.end()); + } + }; + + insertFrames(seg.splitAt(time)); +} + +void AnimationCurve::analyze() +{ + if (isValid()) { + m_minY = std::numeric_limits::max(); + m_maxY = std::numeric_limits::lowest(); + + auto byTime = [](const auto &a, const auto &b) { + return a.position().x() < b.position().x(); + }; + std::sort(m_frames.begin(), m_frames.end(), byTime); + + for (auto e : extrema()) { + if (m_minY > e.y()) + m_minY = e.y(); + + if (m_maxY < e.y()) + m_maxY = e.y(); + } + + for (auto &frame : qAsConst(m_frames)) { + if (frame.position().y() < m_minY) + m_minY = frame.position().y(); + + if (frame.position().y() > m_maxY) + m_maxY = frame.position().y(); + } + } +} + } // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/animationcurve.h b/src/plugins/qmldesigner/components/curveeditor/animationcurve.h index 0533e479a16..18fd3330b0c 100644 --- a/src/plugins/qmldesigner/components/curveeditor/animationcurve.h +++ b/src/plugins/qmldesigner/components/curveeditor/animationcurve.h @@ -27,10 +27,16 @@ #include "keyframe.h" +#include #include +QT_FORWARD_DECLARE_CLASS(QEasingCurve); +QT_FORWARD_DECLARE_CLASS(QPainterPath); + namespace DesignTools { +class CurveSegment; + class AnimationCurve { public: @@ -38,8 +44,12 @@ public: AnimationCurve(const std::vector &frames); + AnimationCurve(const QEasingCurve &easing, const QPointF &start, const QPointF &end); + bool isValid() const; + bool isFromData() const; + double minimumTime() const; double maximumTime() const; @@ -48,6 +58,14 @@ public: double maximumValue() const; + CurveSegment segment(double time) const; + + std::vector segments() const; + + QPainterPath simplePath() const; + + QPainterPath intersectionPath() const; + std::vector keyframes() const; std::vector extrema() const; @@ -56,14 +74,22 @@ public: std::vector xForY(double y, uint segment) const; - bool intersects(const QPointF &coord, double radius); + bool intersects(const QPointF &coord, double radiusX, double radiusY) const; + + void append(const AnimationCurve &other); + + void insert(double time); private: - std::vector m_frames; + void analyze(); + + bool m_fromData; double m_minY; double m_maxY; + + std::vector m_frames; }; } // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp b/src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp index 4eba31c6bd1..2a1ee1c5c7c 100644 --- a/src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp @@ -29,8 +29,11 @@ #include "detail/graphicsview.h" #include "detail/treeview.h" +#include #include +#include #include +#include namespace DesignTools { @@ -44,7 +47,8 @@ CurveEditor::CurveEditor(CurveEditorModel *model, QWidget *parent) splitter->addWidget(m_view); splitter->setStretchFactor(1, 2); - QHBoxLayout *box = new QHBoxLayout; + QVBoxLayout *box = new QVBoxLayout; + box->addWidget(createToolBar()); box->addWidget(splitter); setLayout(box); @@ -61,4 +65,62 @@ void CurveEditor::zoomY(double zoom) m_view->setZoomY(zoom); } +void CurveEditor::clearCanvas() +{ + m_view->reset(m_tree->selection()); +} + +QToolBar *CurveEditor::createToolBar() +{ + QToolBar *bar = new QToolBar; + bar->setFloatable(false); + + QAction *tangentLinearAction = bar->addAction("Linear"); + QAction *tangentStepAction = bar->addAction("Step"); + QAction *tangentSplineAction = bar->addAction("Spline"); + QAction *tangentDefaultAction = bar->addAction("Set Default"); + + auto setLinearInterpolation = [this]() { + m_view->setInterpolation(Keyframe::Interpolation::Linear); + }; + auto setStepInterpolation = [this]() { + m_view->setInterpolation(Keyframe::Interpolation::Step); + }; + auto setSplineInterpolation = [this]() { + m_view->setInterpolation(Keyframe::Interpolation::Bezier); + }; + + connect(tangentLinearAction, &QAction::triggered, setLinearInterpolation); + connect(tangentStepAction, &QAction::triggered, setStepInterpolation); + connect(tangentSplineAction, &QAction::triggered, setSplineInterpolation); + + Q_UNUSED(tangentLinearAction); + Q_UNUSED(tangentSplineAction); + Q_UNUSED(tangentStepAction); + Q_UNUSED(tangentDefaultAction); + + auto *valueBox = new QHBoxLayout; + valueBox->addWidget(new QLabel(tr("Value"))); + valueBox->addWidget(new QDoubleSpinBox); + auto *valueWidget = new QWidget; + valueWidget->setLayout(valueBox); + bar->addWidget(valueWidget); + + auto *durationBox = new QHBoxLayout; + durationBox->addWidget(new QLabel(tr("Duration"))); + durationBox->addWidget(new QSpinBox); + auto *durationWidget = new QWidget; + durationWidget->setLayout(durationBox); + bar->addWidget(durationWidget); + + auto *positionBox = new QHBoxLayout; + positionBox->addWidget(new QLabel(tr("Current Frame"))); + positionBox->addWidget(new QSpinBox); + auto *positionWidget = new QWidget; + positionWidget->setLayout(positionBox); + bar->addWidget(positionWidget); + + return bar; +} + } // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditor.h b/src/plugins/qmldesigner/components/curveeditor/curveeditor.h index a2c5873be0c..c82204d83fa 100644 --- a/src/plugins/qmldesigner/components/curveeditor/curveeditor.h +++ b/src/plugins/qmldesigner/components/curveeditor/curveeditor.h @@ -25,6 +25,7 @@ #pragma once +#include #include namespace DesignTools { @@ -44,7 +45,11 @@ public: void zoomY(double zoom); + void clearCanvas(); + private: + QToolBar *createToolBar(); + TreeView *m_tree; GraphicsView *m_view; diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditor.pri b/src/plugins/qmldesigner/components/curveeditor/curveeditor.pri index 31ffe5d8183..83896f04fc7 100644 --- a/src/plugins/qmldesigner/components/curveeditor/curveeditor.pri +++ b/src/plugins/qmldesigner/components/curveeditor/curveeditor.pri @@ -4,10 +4,10 @@ HEADERS += \ $$PWD/animationcurve.h \ $$PWD/curveeditor.h \ $$PWD/curveeditormodel.h \ + $$PWD/curvesegment.h \ $$PWD/detail/colorcontrol.h \ $$PWD/detail/curveeditorstyledialog.h \ $$PWD/detail/curveitem.h \ - $$PWD/detail/curvesegment.h \ $$PWD/detail/graphicsscene.h \ $$PWD/detail/graphicsview.h \ $$PWD/detail/handleitem.h \ @@ -24,12 +24,12 @@ HEADERS += \ SOURCES += \ $$PWD/animationcurve.cpp \ + $$PWD/curvesegment.cpp \ $$PWD/curveeditor.cpp \ $$PWD/curveeditormodel.cpp \ $$PWD/detail/colorcontrol.cpp \ $$PWD/detail/curveeditorstyledialog.cpp \ $$PWD/detail/curveitem.cpp \ - $$PWD/detail/curvesegment.cpp \ $$PWD/detail/graphicsscene.cpp \ $$PWD/detail/graphicsview.cpp \ $$PWD/detail/handleitem.cpp \ diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h b/src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h index 03ea11c8c1f..5a294e9aa0a 100644 --- a/src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h +++ b/src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h @@ -65,6 +65,7 @@ struct CurveItemStyleOption double width = 1.0; QColor color = QColor(0, 200, 0); QColor selectionColor = QColor(200, 200, 200); + QColor easingCurveColor = QColor(200, 0, 200); }; struct PlayheadStyleOption @@ -84,6 +85,9 @@ struct Shortcuts Shortcut zoom = Shortcut(Qt::RightButton, Qt::AltModifier); Shortcut pan = Shortcut(Qt::MiddleButton, Qt::AltModifier); Shortcut frameAll = Shortcut(Qt::NoModifier, Qt::Key_A); + + Shortcut insertKeyframe = Shortcut(Qt::MiddleButton, Qt::NoModifier); + Shortcut deleteKeyframe = Shortcut(Qt::NoModifier, Qt::Key_Delete); }; struct CurveEditorStyle diff --git a/src/plugins/qmldesigner/components/curveeditor/curvesegment.cpp b/src/plugins/qmldesigner/components/curveeditor/curvesegment.cpp new file mode 100644 index 00000000000..9d70c2a8bf5 --- /dev/null +++ b/src/plugins/qmldesigner/components/curveeditor/curvesegment.cpp @@ -0,0 +1,500 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Design Tooling +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "curvesegment.h" +#include "detail/utils.h" + +#include +#include +#include +#include + +#include + +namespace DesignTools { + +class CubicPolynomial +{ +public: + CubicPolynomial(double p0, double p1, double p2, double p3); + + std::vector extrema() const; + + std::vector roots() const; + +private: + double m_a; + double m_b; + double m_c; + double m_d; +}; + +CubicPolynomial::CubicPolynomial(double p0, double p1, double p2, double p3) + : m_a(p3 - 3.0 * p2 + 3.0 * p1 - p0) + , m_b(3.0 * p2 - 6.0 * p1 + 3.0 * p0) + , m_c(3.0 * p1 - 3.0 * p0) + , m_d(p0) +{} + +std::vector CubicPolynomial::extrema() const +{ + std::vector out; + + auto addValidValue = [&out](double value) { + if (!std::isnan(value) && !std::isinf(value)) + out.push_back(clamp(value, 0.0, 1.0)); + }; + + // Find the roots of the first derivative of y. + auto pd2 = (2.0 * m_b) / (3.0 * m_a) / 2.0; + auto q = m_c / (3.0 * m_a); + + auto radi = std::pow(pd2, 2.0) - q; + + auto x1 = -pd2 + std::sqrt(radi); + auto x2 = -pd2 - std::sqrt(radi); + + addValidValue(x1); + addValidValue(x2); + + return out; +} + +std::vector CubicPolynomial::roots() const +{ + std::vector out; + + auto addValidValue = [&out](double value) { + if (!(std::isnan(value) || std::isinf(value))) + out.push_back(value); + }; + + if (m_a == 0.0) { + // Linear + if (m_b == 0.0) { + if (m_c != 0.0) + out.push_back(-m_d / m_c); + // Quadratic + } else { + const double p = m_c / m_b / 2.0; + const double q = m_d / m_b; + addValidValue(-p + std::sqrt(std::pow(p, 2.0) - q)); + addValidValue(-p - std::sqrt(std::pow(p, 2.0) - q)); + } + // Cubic + } else { + const double p = 3.0 * m_a * m_c - std::pow(m_b, 2.0); + const double q = 2.0 * std::pow(m_b, 3.0) - 9.0 * m_a * m_b * m_c + + 27.0 * std::pow(m_a, 2.0) * m_d; + + auto disc = std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0); + + auto toX = [&](double y) { return (y - m_b) / (3.0 * m_a); }; + + // One real solution. + if (disc >= 0) { + auto u = (1.0 / 2.0) + * std::cbrt(-4.0 * q + + 4.0 * std::sqrt(std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0))); + auto v = (1.0 / 2.0) + * std::cbrt(-4.0 * q + - 4.0 * std::sqrt(std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0))); + + addValidValue(toX(u + v)); + // Three real solutions. + } else { + auto phi = acos(-q / (2 * std::sqrt(-std::pow(p, 3.0)))); + auto y1 = std::sqrt(-p) * 2.0 * cos(phi / 3.0); + auto y2 = std::sqrt(-p) * 2.0 * cos((phi / 3.0) + (2.0 * M_PI / 3.0)); + auto y3 = std::sqrt(-p) * 2.0 * cos((phi / 3.0) + (4.0 * M_PI / 3.0)); + + addValidValue(toX(y1)); + addValidValue(toX(y2)); + addValidValue(toX(y3)); + } + } + return out; +} + +CurveSegment::CurveSegment() + : m_left() + , m_right() +{} + +CurveSegment::CurveSegment(const Keyframe &left, const Keyframe &right) + : m_left(left) + , m_right(right) +{} + +bool CurveSegment::isValid() const +{ + return m_left.position() != m_right.position(); +} + +bool CurveSegment::containsX(double x) const +{ + return m_left.position().x() <= x && m_right.position().x() >= x; +} + +Keyframe CurveSegment::left() const +{ + return m_left; +} + +Keyframe CurveSegment::right() const +{ + return m_right; +} + +Keyframe::Interpolation CurveSegment::interpolation() const +{ + bool invalidBezier = m_right.interpolation() == Keyframe::Interpolation::Bezier + && (!m_left.hasRightHandle() || !m_right.hasLeftHandle()); + + if (m_right.interpolation() == Keyframe::Interpolation::Undefined || invalidBezier) + return Keyframe::Interpolation::Linear; + + return m_right.interpolation(); +} + +double evaluateForT(double t, double p0, double p1, double p2, double p3) +{ + QTC_ASSERT(t >= 0. && t <= 1., return 0.0); + + const double it = 1.0 - t; + + return p0 * std::pow(it, 3.0) + p1 * 3.0 * std::pow(it, 2.0) * t + + p2 * 3.0 * it * std::pow(t, 2.0) + p3 * std::pow(t, 3.0); +} + +QPointF CurveSegment::evaluate(double t) const +{ + if (interpolation() == Keyframe::Interpolation::Linear) { + return lerp(t, m_left.position(), m_right.position()); + } else if (interpolation() == Keyframe::Interpolation::Step) { + if (t == 1.0) + return m_right.position(); + + QPointF br(m_right.position().x(), m_left.position().y()); + return lerp(t, m_left.position(), br); + } else if (interpolation() == Keyframe::Interpolation::Bezier) { + const double x = evaluateForT(t, + m_left.position().x(), + m_left.rightHandle().x(), + m_right.leftHandle().x(), + m_right.position().x()); + + const double y = evaluateForT(t, + m_left.position().y(), + m_left.rightHandle().y(), + m_right.leftHandle().y(), + m_right.position().y()); + + return QPointF(x, y); + } + return QPointF(); +} + +QPainterPath CurveSegment::path() const +{ + QPainterPath path(m_left.position()); + extend(path); + return path; +} + +void CurveSegment::extend(QPainterPath &path) const +{ + if (interpolation() == Keyframe::Interpolation::Linear) { + path.lineTo(m_right.position()); + } else if (interpolation() == Keyframe::Interpolation::Step) { + path.lineTo(QPointF(m_right.position().x(), m_left.position().y())); + path.lineTo(m_right.position()); + } else if (interpolation() == Keyframe::Interpolation::Bezier) { + path.cubicTo(m_left.rightHandle(), m_right.leftHandle(), m_right.position()); + } else if (interpolation() == Keyframe::Interpolation::Easing) { + auto mapEasing = [](const QPointF &start, const QPointF &end, const QPointF &pos) { + QPointF slope(end.x() - start.x(), end.y() - start.y()); + return QPointF(start.x() + slope.x() * pos.x(), start.y() + slope.y() * pos.y()); + }; + + QVariant data = m_right.data(); + if (data.isValid() && data.type() == static_cast(QMetaType::QEasingCurve)) { + QVector points = data.value().toCubicSpline(); + int numSegments = points.count() / 3; + for (int i = 0; i < numSegments; i++) { + QPointF p1 = mapEasing(m_left.position(), m_right.position(), points.at(i * 3)); + QPointF p2 = mapEasing(m_left.position(), m_right.position(), points.at(i * 3 + 1)); + QPointF p3 = mapEasing(m_left.position(), m_right.position(), points.at(i * 3 + 2)); + path.cubicTo(p1, p2, p3); + } + } + } +} + +QEasingCurve CurveSegment::easingCurve() const +{ + auto mapPosition = [this](const QPointF &position) { + QPointF min = m_left.position(); + QPointF max = m_right.position(); + return QPointF((position.x() - min.x()) / (max.x() - min.x()), + (position.y() - min.y()) / (max.y() - min.y())); + }; + + QEasingCurve curve; + curve.addCubicBezierSegment(mapPosition(m_left.rightHandle()), + mapPosition(m_right.leftHandle()), + mapPosition(m_right.position())); + + return curve; +} + +std::vector CurveSegment::extrema() const +{ + std::vector out; + + if (interpolation() == Keyframe::Interpolation::Linear + || interpolation() == Keyframe::Interpolation::Step) { + out.push_back(left().position()); + out.push_back(right().position()); + + } else if (interpolation() == Keyframe::Interpolation::Bezier) { + auto polynomial = CubicPolynomial(m_left.position().y(), + m_left.rightHandle().y(), + m_right.leftHandle().y(), + m_right.position().y()); + + for (double t : polynomial.extrema()) { + const double x = evaluateForT(t, + m_left.position().x(), + m_left.rightHandle().x(), + m_right.leftHandle().x(), + m_right.position().x()); + + const double y = evaluateForT(t, + m_left.position().y(), + m_left.rightHandle().y(), + m_right.leftHandle().y(), + m_right.position().y()); + + out.push_back(QPointF(x, y)); + } + } + return out; +} + +std::vector CurveSegment::tForX(double x) const +{ + if (interpolation() == Keyframe::Interpolation::Linear) { + return {reverseLerp(x, m_right.position().x(), m_left.position().x())}; + } else if (interpolation() == Keyframe::Interpolation::Step) { + return {reverseLerp(x, m_left.position().x(), m_right.position().x())}; + } else if (interpolation() == Keyframe::Interpolation::Bezier) { + auto polynomial = CubicPolynomial(m_left.position().x() - x, + m_left.rightHandle().x() - x, + m_right.leftHandle().x() - x, + m_right.position().x() - x); + + std::vector out; + for (double t : polynomial.roots()) { + if (t >= 0.0 && t <= 1.0) + out.push_back(t); + } + return out; + } + + return {}; +} + +std::vector CurveSegment::tForY(double y) const +{ + auto polynomial = CubicPolynomial(m_left.position().y() - y, + m_left.rightHandle().y() - y, + m_right.leftHandle().y() - y, + m_right.position().y() - y); + + std::vector out; + for (double t : polynomial.roots()) { + if (t >= 0.0 && t <= 1.0) + out.push_back(t); + } + return out; +} + +std::vector CurveSegment::yForX(double x) const +{ + std::vector out; + + auto polynomial = CubicPolynomial(m_left.position().x() - x, + m_left.rightHandle().x() - x, + m_right.leftHandle().x() - x, + m_right.position().x() - x); + + for (double t : polynomial.roots()) { + if (t < 0.0 || t > 1.0) + continue; + + const double y = evaluateForT(t, + m_left.position().y(), + m_left.rightHandle().y(), + m_right.leftHandle().y(), + m_right.position().y()); + + out.push_back(y); + } + + return out; +} + +std::vector CurveSegment::xForY(double y) const +{ + std::vector out; + + auto polynomial = CubicPolynomial(m_left.position().y() - y, + m_left.rightHandle().y() - y, + m_right.leftHandle().y() - y, + m_right.position().y() - y); + + for (double t : polynomial.roots()) { + if (t < 0.0 || t > 1.0) + continue; + + const double x = evaluateForT(t, + m_left.position().x(), + m_left.rightHandle().x(), + m_right.leftHandle().x(), + m_right.position().x()); + + out.push_back(x); + } + + return out; +} + +std::array CurveSegment::splitAt(double time) +{ + std::array out; + if (interpolation() == Keyframe::Interpolation::Linear) { + for (double t : tForX(time)) { + out[0] = left(); + out[1] = Keyframe(lerp(t, left().position(), right().position())); + out[2] = right(); + + out[1].setInterpolation(Keyframe::Interpolation::Linear); + return out; + } + + } else if (interpolation() == Keyframe::Interpolation::Step) { + out[0] = left(); + out[1] = Keyframe(QPointF(left().position() + QPointF(time, 0.0))); + out[2] = right(); + + out[1].setInterpolation(Keyframe::Interpolation::Step); + + } else if (interpolation() == Keyframe::Interpolation::Bezier) { + for (double t : tForX(time)) { + auto p0 = lerp(t, left().position(), left().rightHandle()); + auto p1 = lerp(t, left().rightHandle(), right().leftHandle()); + auto p2 = lerp(t, right().leftHandle(), right().position()); + + auto p01 = lerp(t, p0, p1); + auto p12 = lerp(t, p1, p2); + auto p01p12 = lerp(t, p01, p12); + + out[0] = Keyframe(left().position(), left().leftHandle(), p0); + out[1] = Keyframe(p01p12, p01, p12); + out[2] = Keyframe(right().position(), p2, right().rightHandle()); + + out[0].setInterpolation(left().interpolation()); + out[0].setData(left().data()); + + out[2].setInterpolation(right().interpolation()); + out[2].setData(right().data()); + return out; + } + } + return out; +} + +bool CurveSegment::intersects(const QPointF &coord, double radiusX, double radiusY) const +{ + if (interpolation() == Keyframe::Interpolation::Linear) { + for (auto &t : tForX(coord.x())) { + QLineF line(evaluate(t), coord); + if (std::abs(line.dy()) < radiusY) + return true; + } + } else if (interpolation() == Keyframe::Interpolation::Step) { + if (coord.x() > (right().position().x() - radiusX)) + return true; + + if (coord.y() > (left().position().y() - radiusY) + && coord.y() < (left().position().y() + radiusY)) + return true; + + } else if (interpolation() == Keyframe::Interpolation::Bezier) { + for (auto &y : yForX(coord.x())) { + QLineF line(coord.x(), y, coord.x(), coord.y()); + if (line.length() < radiusY) + return true; + } + + for (auto &x : xForY(coord.y())) { + QLineF line(x, coord.y(), coord.x(), coord.y()); + if (line.length() < radiusX) + return true; + } + } + return false; +} + +void CurveSegment::setLeft(const Keyframe &frame) +{ + m_left = frame; +} + +void CurveSegment::setRight(const Keyframe &frame) +{ + m_right = frame; +} + +void CurveSegment::setInterpolation(const Keyframe::Interpolation &interpol) +{ + m_right.setInterpolation(interpol); + + if (interpol == Keyframe::Interpolation::Bezier) { + double distance = QLineF(m_left.position(), m_right.position()).length() / 3.0; + if (!m_left.hasRightHandle()) + m_left.setRightHandle(m_left.position() + QPointF(distance, 0.0)); + + if (!m_right.hasLeftHandle()) + m_right.setLeftHandle(m_right.position() - QPointF(distance, 0.0)); + + } else { + m_left.setRightHandle(QPointF()); + m_right.setLeftHandle(QPointF()); + } +} + +} // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.h b/src/plugins/qmldesigner/components/curveeditor/curvesegment.h similarity index 75% rename from src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.h rename to src/plugins/qmldesigner/components/curveeditor/curvesegment.h index 5dbce58bccd..dd5f9f8c550 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.h +++ b/src/plugins/qmldesigner/components/curveeditor/curvesegment.h @@ -27,10 +27,13 @@ #include "keyframe.h" +#include #include QT_BEGIN_NAMESPACE class QPointF; +class QEasingCurve; +class QPainterPath; QT_END_NAMESPACE namespace DesignTools { @@ -42,20 +45,44 @@ public: CurveSegment(const Keyframe &first, const Keyframe &last); + bool isValid() const; + bool containsX(double x) const; + Keyframe left() const; + + Keyframe right() const; + + Keyframe::Interpolation interpolation() const; + QPointF evaluate(double t) const; + QPainterPath path() const; + + void extend(QPainterPath &path) const; + + QEasingCurve easingCurve() const; + std::vector extrema() const; + std::vector tForX(double x) const; + + std::vector tForY(double y) const; + std::vector yForX(double x) const; std::vector xForY(double y) const; + std::array splitAt(double time); + + bool intersects(const QPointF &coord, double radiusX, double radiusY) const; + void setLeft(const Keyframe &frame); void setRight(const Keyframe &frame); + void setInterpolation(const Keyframe::Interpolation &interpol); + private: Keyframe m_left; diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.cpp index 38efd29571b..b327e18c01f 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.cpp @@ -28,9 +28,11 @@ #include "keyframeitem.h" #include "utils.h" +#include #include #include +#include #include namespace DesignTools { @@ -39,42 +41,43 @@ CurveItem::CurveItem(QGraphicsItem *parent) : QGraphicsObject(parent) , m_id(0) , m_style() + , m_type(ValueType::Undefined) + , m_component(PropertyTreeItem::Component::Generic) , m_transform() , m_keyframes() , m_underMouse(false) , m_itemDirty(false) - , m_pathDirty(true) {} CurveItem::CurveItem(unsigned int id, const AnimationCurve &curve, QGraphicsItem *parent) : QGraphicsObject(parent) , m_id(id) , m_style() + , m_type(ValueType::Undefined) + , m_component(PropertyTreeItem::Component::Generic) , m_transform() , m_keyframes() , m_underMouse(false) , m_itemDirty(false) - , m_pathDirty(true) { setAcceptHoverEvents(true); setFlag(QGraphicsItem::ItemIsMovable, false); - auto emitCurveChanged = [this]() { - m_itemDirty = true; - m_pathDirty = true; - update(); - }; - for (auto frame : curve.keyframes()) { auto *item = new KeyframeItem(frame, this); - QObject::connect(item, &KeyframeItem::redrawCurve, emitCurveChanged); + QObject::connect(item, &KeyframeItem::redrawCurve, this, &CurveItem::emitCurveChanged); m_keyframes.push_back(item); } } CurveItem::~CurveItem() {} +bool CurveItem::isUnderMouse() const +{ + return m_underMouse; +} + int CurveItem::type() const { return Type; @@ -100,10 +103,11 @@ bool CurveItem::contains(const QPointF &point) const bool valid = false; QPointF transformed(m_transform.inverted(&valid).map(point)); - double width = std::abs(20.0 / scaleY(m_transform)); + double widthX = std::abs(10.0 / scaleX(m_transform)); + double widthY = std::abs(10.0 / scaleY(m_transform)); if (valid) - return curve().intersects(transformed, width); + return curve().intersects(transformed, widthX, widthY); return false; } @@ -112,15 +116,25 @@ void CurveItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidg { if (m_keyframes.size() > 1) { QPen pen = painter->pen(); - QColor col = m_underMouse ? Qt::red : m_style.color; - pen.setWidthF(m_style.width); - pen.setColor(hasSelection() ? m_style.selectionColor : col); painter->save(); painter->setPen(pen); - painter->drawPath(path()); + for (auto &&segment : curve().segments()) { + if (segment.interpolation() == Keyframe::Interpolation::Easing) { + pen.setColor(m_style.easingCurveColor); + } else { + if (m_underMouse) + pen.setColor(Qt::red); + else if (hasSelection()) + pen.setColor(m_style.selectionColor); + else + pen.setColor(m_style.color); + } + painter->setPen(pen); + painter->drawPath(m_transform.map(segment.path())); + } painter->restore(); } } @@ -136,7 +150,6 @@ bool CurveItem::hasSelection() const if (frame->selected()) return true; } - return false; } @@ -145,52 +158,145 @@ unsigned int CurveItem::id() const return m_id; } -QPainterPath CurveItem::path() const +ValueType CurveItem::valueType() const { - if (m_pathDirty) { - Keyframe previous = m_keyframes.front()->keyframe(); - Keyframe current; + return m_type; +} - m_path = QPainterPath(m_transform.map(previous.position())); - for (size_t i = 1; i < m_keyframes.size(); ++i) { - current = m_keyframes[i]->keyframe(); - - if (previous.rightHandle().isNull() || current.leftHandle().isNull()) { - m_path.lineTo(m_transform.map(current.position())); - } else { - m_path.cubicTo( - m_transform.map(previous.rightHandle()), - m_transform.map(current.leftHandle()), - m_transform.map(current.position())); - } - - previous = current; - } - m_pathDirty = false; - } - - return m_path; +PropertyTreeItem::Component CurveItem::component() const +{ + return m_component; } AnimationCurve CurveItem::curve() const { - std::vector out; - out.reserve(m_keyframes.size()); - for (auto item : m_keyframes) - out.push_back(item->keyframe()); + std::vector frames; + frames.reserve(m_keyframes.size()); + for (auto *frameItem : m_keyframes) + frames.push_back(frameItem->keyframe()); + + return AnimationCurve(frames); +} + +AnimationCurve CurveItem::resolvedCurve() const +{ + std::vector tmp = curves(); + + if (tmp.size() == 0) + return AnimationCurve(); + + if (tmp.size() == 1) + return tmp[0]; + + AnimationCurve out = tmp[0]; + for (size_t i = 1; i < tmp.size(); ++i) + out.append(tmp[i]); return out; } +std::vector CurveItem::curves() const +{ + std::vector out; + + std::vector tmp; + + for (size_t i = 0; i < m_keyframes.size(); ++i) { + KeyframeItem *item = m_keyframes[i]; + + Keyframe current = item->keyframe(); + + if (current.interpolation() == Keyframe::Interpolation::Easing && i > 0) { + if (!tmp.empty()) { + Keyframe previous = tmp.back(); + + if (tmp.size() >= 2) + out.push_back(AnimationCurve(tmp)); + + out.push_back( + AnimationCurve( + current.data().value(), + previous.position(), + current.position())); + + tmp.clear(); + tmp.push_back(current); + } + } else { + tmp.push_back(current); + } + } + + if (tmp.size() >= 2) + out.push_back(AnimationCurve(tmp)); + + return out; +} + +void CurveItem::restore() +{ + if (m_keyframes.empty()) + return; + + auto byTime = [](auto a, auto b) { + return a->keyframe().position().x() < b->keyframe().position().x(); + }; + std::sort(m_keyframes.begin(), m_keyframes.end(), byTime); + + KeyframeItem *prevItem = m_keyframes[0]; + for (size_t i = 1; i < m_keyframes.size(); ++i) { + KeyframeItem *currItem = m_keyframes[i]; + + Keyframe prev = prevItem->keyframe(); + Keyframe curr = currItem->keyframe(); + CurveSegment segment(prev, curr); + + segment.setInterpolation(segment.interpolation()); + + prevItem->setRightHandle(segment.left().rightHandle()); + currItem->setLeftHandle(segment.right().leftHandle()); + + prevItem = currItem; + } +} + void CurveItem::setDirty(bool dirty) { m_itemDirty = dirty; } +void CurveItem::setHandleVisibility(bool visible) +{ + for (auto frame : m_keyframes) + frame->setHandleVisibility(visible); +} + +void CurveItem::setValueType(ValueType type) +{ + m_type = type; +} + +void CurveItem::setComponent(PropertyTreeItem::Component comp) +{ + m_component = comp; +} + +void CurveItem::setCurve(const AnimationCurve &curve) +{ + freeClear(m_keyframes); + + for (auto frame : curve.keyframes()) { + auto *item = new KeyframeItem(frame, this); + item->setComponentTransform(m_transform); + m_keyframes.push_back(item); + QObject::connect(item, &KeyframeItem::redrawCurve, this, &CurveItem::emitCurveChanged); + } + + emitCurveChanged(); +} + QRectF CurveItem::setComponentTransform(const QTransform &transform) { - m_pathDirty = true; - prepareGeometryChange(); m_transform = transform; for (auto frame : m_keyframes) @@ -207,6 +313,30 @@ void CurveItem::setStyle(const CurveEditorStyle &style) frame->setStyle(style); } +void CurveItem::setInterpolation(Keyframe::Interpolation interpolation) +{ + if (m_keyframes.empty()) + return; + + KeyframeItem *prevItem = m_keyframes[0]; + for (size_t i = 1; i < m_keyframes.size(); ++i) { + KeyframeItem *currItem = m_keyframes[i]; + if (currItem->selected()) { + Keyframe prev = prevItem->keyframe(); + Keyframe curr = currItem->keyframe(); + CurveSegment segment(prev, curr); + + segment.setInterpolation(interpolation); + prevItem->setKeyframe(segment.left()); + currItem->setKeyframe(segment.right()); + + m_itemDirty = true; + } + + prevItem = currItem; + } +} + void CurveItem::connect(GraphicsScene *scene) { for (auto *frame : m_keyframes) { @@ -223,4 +353,31 @@ void CurveItem::setIsUnderMouse(bool under) } } +void CurveItem::insertKeyframeByTime(double time) +{ + AnimationCurve acurve = curve(); + acurve.insert(time); + setCurve(acurve); +} + +void CurveItem::deleteSelectedKeyframes() +{ + for (auto *&item : m_keyframes) { + if (item->selected()) { + delete item; + item = nullptr; + } + } + auto isNullptr = [](KeyframeItem *frame) { return frame == nullptr; }; + auto iter = std::remove_if(m_keyframes.begin(), m_keyframes.end(), isNullptr); + m_keyframes.erase(iter, m_keyframes.end()); + emitCurveChanged(); +} + +void CurveItem::emitCurveChanged() +{ + m_itemDirty = true; + update(); +} + } // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.h b/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.h index 90e68e20f1c..a83b5304639 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.h +++ b/src/plugins/qmldesigner/components/curveeditor/detail/curveitem.h @@ -26,7 +26,10 @@ #pragma once #include "curveeditorstyle.h" +#include "curvesegment.h" +#include "keyframe.h" #include "selectableitem.h" +#include "treeitem.h" #include @@ -59,29 +62,59 @@ public: bool isDirty() const; + bool isUnderMouse() const; + bool hasSelection() const; unsigned int id() const; + ValueType valueType() const; + + PropertyTreeItem::Component component() const; + AnimationCurve curve() const; + AnimationCurve resolvedCurve() const; + + std::vector curves() const; + + void restore(); + void setDirty(bool dirty); + void setHandleVisibility(bool visible); + + void setValueType(ValueType type); + + void setComponent(PropertyTreeItem::Component comp); + + void setCurve(const AnimationCurve &curve); + QRectF setComponentTransform(const QTransform &transform); void setStyle(const CurveEditorStyle &style); + void setInterpolation(Keyframe::Interpolation interpolation); + void connect(GraphicsScene *scene); void setIsUnderMouse(bool under); + void insertKeyframeByTime(double time); + + void deleteSelectedKeyframes(); + private: - QPainterPath path() const; + void emitCurveChanged(); unsigned int m_id; CurveItemStyleOption m_style; + ValueType m_type; + + PropertyTreeItem::Component m_component; + QTransform m_transform; std::vector m_keyframes; @@ -89,10 +122,6 @@ private: bool m_underMouse; bool m_itemDirty; - - mutable bool m_pathDirty; - - mutable QPainterPath m_path; }; } // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.cpp deleted file mode 100644 index 40f675f3ecb..00000000000 --- a/src/plugins/qmldesigner/components/curveeditor/detail/curvesegment.cpp +++ /dev/null @@ -1,277 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Design Tooling -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ - -#include "curvesegment.h" -#include "utils.h" - -#include - -#include - -namespace DesignTools { - -class CubicPolynomial -{ -public: - CubicPolynomial(double p0, double p1, double p2, double p3); - - std::vector extrema() const; - - std::vector roots() const; - -private: - double m_a; - double m_b; - double m_c; - double m_d; -}; - -CubicPolynomial::CubicPolynomial(double p0, double p1, double p2, double p3) - : m_a(p3 - 3.0 * p2 + 3.0 * p1 - p0) - , m_b(3.0 * p2 - 6.0 * p1 + 3.0 * p0) - , m_c(3.0 * p1 - 3.0 * p0) - , m_d(p0) -{} - -std::vector CubicPolynomial::extrema() const -{ - std::vector out; - - auto addValidValue = [&out](double value) { - if (!std::isnan(value) && !std::isinf(value)) - out.push_back(clamp(value, 0.0, 1.0)); - }; - - // Find the roots of the first derivative of y. - auto pd2 = (2.0 * m_b) / (3.0 * m_a) / 2.0; - auto q = m_c / (3.0 * m_a); - - auto radi = std::pow(pd2, 2.0) - q; - - auto x1 = -pd2 + std::sqrt(radi); - auto x2 = -pd2 - std::sqrt(radi); - - addValidValue(x1); - addValidValue(x2); - - return out; -} - -std::vector CubicPolynomial::roots() const -{ - std::vector out; - - auto addValidValue = [&out](double value) { - if (!(std::isnan(value) || std::isinf(value))) - out.push_back(value); - }; - - if (m_a == 0.0) { - // Linear - if (m_b == 0.0) { - if (m_c != 0.0) - out.push_back(-m_d / m_c); - // Quadratic - } else { - const double p = m_c / m_b / 2.0; - const double q = m_d / m_b; - addValidValue(-p + std::sqrt(std::pow(p, 2.0) - q)); - addValidValue(-p - std::sqrt(std::pow(p, 2.0) - q)); - } - // Cubic - } else { - const double p = 3.0 * m_a * m_c - std::pow(m_b, 2.0); - const double q = 2.0 * std::pow(m_b, 3.0) - 9.0 * m_a * m_b * m_c - + 27.0 * std::pow(m_a, 2.0) * m_d; - - auto disc = std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0); - - auto toX = [&](double y) { return (y - m_b) / (3.0 * m_a); }; - - // One real solution. - if (disc >= 0) { - auto u = (1.0 / 2.0) - * std::cbrt(-4.0 * q - + 4.0 * std::sqrt(std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0))); - auto v = (1.0 / 2.0) - * std::cbrt(-4.0 * q - - 4.0 * std::sqrt(std::pow(q, 2.0) + 4.0 * std::pow(p, 3.0))); - - addValidValue(toX(u + v)); - // Three real solutions. - } else { - auto phi = acos(-q / (2 * std::sqrt(-std::pow(p, 3.0)))); - auto y1 = std::sqrt(-p) * 2.0 * cos(phi / 3.0); - auto y2 = std::sqrt(-p) * 2.0 * cos((phi / 3.0) + (2.0 * M_PI / 3.0)); - auto y3 = std::sqrt(-p) * 2.0 * cos((phi / 3.0) + (4.0 * M_PI / 3.0)); - - addValidValue(toX(y1)); - addValidValue(toX(y2)); - addValidValue(toX(y3)); - } - } - return out; -} - -CurveSegment::CurveSegment() - : m_left() - , m_right() -{} - -CurveSegment::CurveSegment(const Keyframe &left, const Keyframe &right) - : m_left(left) - , m_right(right) -{} - -bool CurveSegment::containsX(double x) const -{ - return m_left.position().x() <= x && m_right.position().x() >= x; -} - -double evaluateForT(double t, double p0, double p1, double p2, double p3) -{ - assert(t >= 0. && t <= 1.); - - const double it = 1.0 - t; - - return p0 * std::pow(it, 3.0) + p1 * 3.0 * std::pow(it, 2.0) * t - + p2 * 3.0 * it * std::pow(t, 2.0) + p3 * std::pow(t, 3.0); -} - -QPointF CurveSegment::evaluate(double t) const -{ - const double x = evaluateForT( - t, - m_left.position().x(), - m_left.rightHandle().x(), - m_right.leftHandle().x(), - m_right.position().x()); - - const double y = evaluateForT( - t, - m_left.position().y(), - m_left.rightHandle().y(), - m_right.leftHandle().y(), - m_right.position().y()); - - return QPointF(x, y); -} - -std::vector CurveSegment::extrema() const -{ - std::vector out; - - auto polynomial = CubicPolynomial( - m_left.position().y(), - m_left.rightHandle().y(), - m_right.leftHandle().y(), - m_right.position().y()); - - for (double t : polynomial.extrema()) { - - const double x = evaluateForT( - t, - m_left.position().x(), - m_left.rightHandle().x(), - m_right.leftHandle().x(), - m_right.position().x()); - - const double y = evaluateForT( - t, - m_left.position().y(), - m_left.rightHandle().y(), - m_right.leftHandle().y(), - m_right.position().y()); - - out.push_back(QPointF(x, y)); - } - return out; -} - -std::vector CurveSegment::yForX(double x) const -{ - std::vector out; - - auto polynomial = CubicPolynomial( - m_left.position().x() - x, - m_left.rightHandle().x() - x, - m_right.leftHandle().x() - x, - m_right.position().x() - x); - - for (double t : polynomial.roots()) { - if (t < 0.0 || t > 1.0) - continue; - - const double y = evaluateForT( - t, - m_left.position().y(), - m_left.rightHandle().y(), - m_right.leftHandle().y(), - m_right.position().y()); - - out.push_back(y); - } - - return out; -} - -std::vector CurveSegment::xForY(double y) const -{ - std::vector out; - - auto polynomial = CubicPolynomial( - m_left.position().y() - y, - m_left.rightHandle().y() - y, - m_right.leftHandle().y() - y, - m_right.position().y() - y); - - for (double t : polynomial.roots()) { - if (t < 0.0 || t > 1.0) - continue; - - const double x = evaluateForT( - t, - m_left.position().x(), - m_left.rightHandle().x(), - m_right.leftHandle().x(), - m_right.position().x()); - - out.push_back(x); - } - - return out; -} - -void CurveSegment::setLeft(const Keyframe &frame) -{ - m_left = frame; -} - -void CurveSegment::setRight(const Keyframe &frame) -{ - m_right = frame; -} - -} // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.cpp index 096e57aafab..cf88397a46d 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsscene.cpp @@ -141,6 +141,10 @@ void GraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) const auto itemList = items(); for (auto *item : itemList) { if (auto *curveItem = qgraphicsitem_cast(item)) { + + // CurveItems might becom invalid after a keyframe-drag operation. + curveItem->restore(); + if (curveItem->contains(mouseEvent->scenePos())) curveItem->setSelected(true); @@ -201,7 +205,7 @@ QRectF GraphicsScene::limits() const const auto itemList = items(); for (auto *item : itemList) { if (auto *curveItem = qgraphicsitem_cast(item)) { - auto curve = curveItem->curve(); + auto curve = curveItem->resolvedCurve(); if (min.x() > curve.minimumTime()) min.rx() = curve.minimumTime(); diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp index ae60888d1f7..49321807d74 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp @@ -209,6 +209,18 @@ void GraphicsView::reset(const std::vector &items) viewport()->update(); } +void GraphicsView::setInterpolation(Keyframe::Interpolation interpol) +{ + const auto itemList = items(); + for (auto *item : itemList) { + if (auto *citem = qgraphicsitem_cast(item)) + if (citem->hasSelection()) + citem->setInterpolation(interpol); + } + + viewport()->update(); +} + void GraphicsView::resizeEvent(QResizeEvent *event) { QGraphicsView::resizeEvent(event); @@ -220,6 +232,8 @@ void GraphicsView::keyPressEvent(QKeyEvent *event) Shortcut shortcut(event->modifiers(), static_cast(event->key())); if (shortcut == m_style.shortcuts.frameAll) applyZoom(0.0, 0.0); + else if (shortcut == m_style.shortcuts.deleteKeyframe) + deleteSelectedKeyframes(); } void GraphicsView::mousePressEvent(QMouseEvent *event) @@ -228,6 +242,11 @@ void GraphicsView::mousePressEvent(QMouseEvent *event) return; Shortcut shortcut(event); + if (shortcut == m_style.shortcuts.insertKeyframe) { + insertKeyframe(globalToRaster(event->globalPos()).x()); + return; + } + if (shortcut == Shortcut(Qt::LeftButton)) { QPointF pos = mapToScene(event->pos()); if (timeScaleRect().contains(pos)) { @@ -355,6 +374,7 @@ void GraphicsView::applyZoom(double x, double y, const QPoint &pivot) double minValue = minimumValue(); double maxValue = maximumValue(); + QRectF canvas = canvasRect(); double xZoomedOut = canvas.width() / (maxTime - minTime); @@ -366,7 +386,6 @@ void GraphicsView::applyZoom(double x, double y, const QPoint &pivot) double scaleY = lerp(clamp(m_zoomY, 0.0, 1.0), -yZoomedOut, -yZoomedIn); m_transform = QTransform::fromScale(scaleX, scaleY); - m_scene.setComponentTransform(m_transform); QRectF sr = m_scene.sceneRect().adjusted( @@ -385,13 +404,32 @@ void GraphicsView::applyZoom(double x, double y, const QPoint &pivot) } } +void GraphicsView::insertKeyframe(double time) +{ + const auto itemList = items(); + for (auto *item : itemList) { + if (auto *curveItem = qgraphicsitem_cast(item)) { + if (curveItem->isUnderMouse()) + curveItem->insertKeyframeByTime(std::round(time)); + } + } +} + +void GraphicsView::deleteSelectedKeyframes() +{ + const auto itemList = items(); + for (auto *item : itemList) { + if (auto *curveItem = qgraphicsitem_cast(item)) + curveItem->deleteSelectedKeyframes(); + } +} + void GraphicsView::drawGrid(QPainter *painter, const QRectF &rect) { - QRectF gridRect = rect.adjusted( - m_style.valueAxisWidth + m_style.canvasMargin, - m_style.timeAxisHeight + m_style.canvasMargin, - -m_style.canvasMargin, - -m_style.canvasMargin); + QRectF gridRect = rect.adjusted(m_style.valueAxisWidth + m_style.canvasMargin, + m_style.timeAxisHeight + m_style.canvasMargin, + -m_style.canvasMargin, + -m_style.canvasMargin); if (!gridRect.isValid()) return; diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.h b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.h index 1975e3696db..15c8aeb75df 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.h +++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.h @@ -100,6 +100,8 @@ public: void reset(const std::vector &items); + void setInterpolation(Keyframe::Interpolation interpol); + protected: void resizeEvent(QResizeEvent *event) override; @@ -122,6 +124,10 @@ protected: private: void applyZoom(double x, double y, const QPoint &pivot = QPoint()); + void insertKeyframe(double time); + + void deleteSelectedKeyframes(); + void drawGrid(QPainter *painter, const QRectF &rect); void drawExtremaX(QPainter *painter, const QRectF &rect); diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp index c54b26e2795..a5af39efbbe 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp @@ -24,6 +24,7 @@ ****************************************************************************/ #include "handleitem.h" +#include "keyframeitem.h" #include "utils.h" #include @@ -101,4 +102,24 @@ void HandleItem::setStyle(const CurveEditorStyle &style) m_style = style.handleStyle; } +QVariant HandleItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionChange) { + if (KeyframeItem *parent = qgraphicsitem_cast(parentItem())) { + HandleSlot slot = parent->handleSlot(this); + QPointF pos = value.toPointF(); + if (slot == HandleSlot::Left) { + if (pos.x() > 0.0) + pos.rx() = 0.0; + + } else if (slot == HandleSlot::Right) { + if (pos.x() < 0.0) + pos.rx() = 0.0; + } + return QVariant(pos); + } + } + return QGraphicsItem::itemChange(change, value); +} + } // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.h b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.h index 4c9126c629d..32b27b9c552 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.h +++ b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.h @@ -49,6 +49,9 @@ public: void setStyle(const CurveEditorStyle &style); +protected: + QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; + private: HandleItemStyleOption m_style; }; diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp index 94cdbbcbb16..236fa5b484b 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp @@ -24,6 +24,7 @@ ****************************************************************************/ #include "keyframeitem.h" +#include "curveitem.h" #include "handleitem.h" #include @@ -40,31 +41,11 @@ KeyframeItem::KeyframeItem(QGraphicsItem *parent) KeyframeItem::KeyframeItem(const Keyframe &keyframe, QGraphicsItem *parent) : SelectableItem(parent) , m_transform() - , m_frame(keyframe) - , m_left(keyframe.hasLeftHandle() ? new HandleItem(this) : nullptr) - , m_right(keyframe.hasRightHandle() ? new HandleItem(this) : nullptr) + , m_frame() + , m_left(nullptr) + , m_right(nullptr) { - auto updatePosition = [this]() { this->updatePosition(true); }; - connect(this, &QGraphicsObject::xChanged, updatePosition); - connect(this, &QGraphicsObject::yChanged, updatePosition); - - if (m_left) { - m_left->setPos(m_frame.leftHandle() - m_frame.position()); - auto updateLeftHandle = [this]() { updateHandle(m_left); }; - connect(m_left, &QGraphicsObject::xChanged, updateLeftHandle); - connect(m_left, &QGraphicsObject::yChanged, updateLeftHandle); - m_left->hide(); - } - - if (m_right) { - m_right->setPos(m_frame.rightHandle() - m_frame.position()); - auto updateRightHandle = [this]() { updateHandle(m_right); }; - connect(m_right, &QGraphicsObject::xChanged, updateRightHandle); - connect(m_right, &QGraphicsObject::yChanged, updateRightHandle); - m_right->hide(); - } - - setPos(m_frame.position()); + setKeyframe(keyframe); } int KeyframeItem::type() const @@ -101,6 +82,33 @@ Keyframe KeyframeItem::keyframe() const return m_frame; } +HandleSlot KeyframeItem::handleSlot(HandleItem *item) const +{ + if (item == m_left) + return HandleSlot::Left; + else if (item == m_right) + return HandleSlot::Right; + else + return HandleSlot::Undefined; +} + +void KeyframeItem::setHandleVisibility(bool visible) +{ + m_visibleOverride = visible; + + if (visible) { + if (m_left) + m_left->show(); + if (m_right) + m_right->show(); + } else { + if (m_left) + m_left->hide(); + if (m_right) + m_right->hide(); + } +} + void KeyframeItem::setComponentTransform(const QTransform &transform) { m_transform = transform; @@ -125,6 +133,59 @@ void KeyframeItem::setStyle(const CurveEditorStyle &style) m_right->setStyle(style); } +void KeyframeItem::setKeyframe(const Keyframe &keyframe) +{ + bool needsConnection = m_frame.position().isNull(); + + m_frame = keyframe; + + if (needsConnection) { + auto updatePosition = [this]() { this->updatePosition(true); }; + connect(this, &QGraphicsObject::xChanged, updatePosition); + connect(this, &QGraphicsObject::yChanged, updatePosition); + } + + if (m_frame.hasLeftHandle()) { + if (!m_left) { + m_left = new HandleItem(this); + auto updateLeftHandle = [this]() { updateHandle(m_left); }; + connect(m_left, &QGraphicsObject::xChanged, updateLeftHandle); + connect(m_left, &QGraphicsObject::yChanged, updateLeftHandle); + } + m_left->setPos(m_transform.map(m_frame.leftHandle() - m_frame.position())); + } else if (m_left) { + delete m_left; + m_left = nullptr; + } + + if (m_frame.hasRightHandle()) { + if (!m_right) { + m_right = new HandleItem(this); + auto updateRightHandle = [this]() { updateHandle(m_right); }; + connect(m_right, &QGraphicsObject::xChanged, updateRightHandle); + connect(m_right, &QGraphicsObject::yChanged, updateRightHandle); + } + m_right->setPos(m_transform.map(m_frame.rightHandle() - m_frame.position())); + } else if (m_right) { + delete m_right; + m_right = nullptr; + } + + setPos(m_transform.map(m_frame.position())); +} + +void KeyframeItem::setLeftHandle(const QPointF &pos) +{ + m_frame.setLeftHandle(pos); + setKeyframe(m_frame); +} + +void KeyframeItem::setRightHandle(const QPointF &pos) +{ + m_frame.setRightHandle(pos); + setKeyframe(m_frame); +} + void KeyframeItem::updatePosition(bool update) { bool ok = false; @@ -160,6 +221,9 @@ void KeyframeItem::moveKeyframe(const QPointF &direction) void KeyframeItem::moveHandle(HandleSlot handle, double deltaAngle, double deltaLength) { auto move = [this, deltaAngle, deltaLength](HandleItem *item) { + if (!item) + return; + QLineF current(QPointF(0.0, 0.0), item->pos()); current.setAngle(current.angle() + deltaAngle); current.setLength(current.length() + deltaLength); @@ -225,6 +289,14 @@ QVariant KeyframeItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons QPointF position = m_transform.inverted(&ok).map(value.toPointF()); if (ok) { position.setX(std::round(position.x())); + + if (auto *curveItem = qgraphicsitem_cast(parentItem())) { + if (curveItem->valueType() == ValueType::Integer) + position.setY(std::round(position.y())); + else if (curveItem->valueType() == ValueType::Bool) + position.setY(position.y() > 0.5 ? 1.0 : 0.0); + } + return QVariant(m_transform.map(position)); } } @@ -232,19 +304,33 @@ QVariant KeyframeItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons return QGraphicsItem::itemChange(change, value); } +void KeyframeItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + SelectableItem::mousePressEvent(event); + + if (auto *curveItem = qgraphicsitem_cast(parentItem())) + curveItem->setHandleVisibility(false); +} + +void KeyframeItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + SelectableItem::mouseReleaseEvent(event); + if (auto *curveItem = qgraphicsitem_cast(parentItem())) + curveItem->setHandleVisibility(true); +} + void KeyframeItem::selectionCallback() { - auto setHandleVisibility = [](HandleItem *handle, bool visible) { - if (handle) - handle->setVisible(visible); - }; - if (selected()) { - setHandleVisibility(m_left, true); - setHandleVisibility(m_right, true); + if (m_visibleOverride) { + setHandleVisibility(true); + setHandleVisibility(true); + } } else { - setHandleVisibility(m_left, false); - setHandleVisibility(m_right, false); + if (!m_visibleOverride) { + setHandleVisibility(false); + setHandleVisibility(false); + } } } diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.h b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.h index ae65be1e9bf..55109f435ad 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.h +++ b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.h @@ -65,10 +65,20 @@ public: Keyframe keyframe() const; + HandleSlot handleSlot(HandleItem *item) const; + + void setHandleVisibility(bool visible); + void setComponentTransform(const QTransform &transform); void setStyle(const CurveEditorStyle &style); + void setKeyframe(const Keyframe &keyframe); + + void setLeftHandle(const QPointF &pos); + + void setRightHandle(const QPointF &pos); + void moveKeyframe(const QPointF &direction); void moveHandle(HandleSlot handle, double deltaAngle, double deltaLength); @@ -76,6 +86,10 @@ public: protected: QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + void selectionCallback() override; private: @@ -93,6 +107,8 @@ private: HandleItem *m_left; HandleItem *m_right; + + bool m_visibleOverride = true; }; } // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.cpp index fb616820906..2f23360bab1 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/selectableitem.cpp @@ -26,8 +26,6 @@ #include "selectableitem.h" #include "keyframeitem.h" -#include - namespace DesignTools { SelectableItem::SelectableItem(QGraphicsItem *parent) diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/selector.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/selector.cpp index 633c1ddd395..6119e8e9960 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/selector.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/selector.cpp @@ -77,10 +77,10 @@ void Selector::mouseMove(QMouseEvent *event, GraphicsView *view, Playhead &playh if (m_mouseInit.isNull()) return; - QPointF delta = event->globalPos() - m_mouseInit; - if (delta.manhattanLength() < QApplication::startDragDistance()) + if ((event->globalPos() - m_mouseInit).manhattanLength() < QApplication::startDragDistance()) return; + QPointF delta = event->globalPos() - m_mouseCurr; if (m_shortcut == m_shortcuts.newSelection || m_shortcut == m_shortcuts.addToSelection || m_shortcut == m_shortcuts.removeFromSelection || m_shortcut == m_shortcuts.toggleSelection) { diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/treemodel.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/treemodel.cpp index 1ea037091d6..2e1b3f0a096 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/treemodel.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/treemodel.cpp @@ -24,8 +24,8 @@ ****************************************************************************/ #include "treemodel.h" -#include "treeitem.h" #include "detail/graphicsview.h" +#include "treeitem.h" #include diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/treeview.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/treeview.cpp index 3b9a4ef7c5d..9dd99f0bc1d 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/treeview.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/treeview.cpp @@ -42,7 +42,6 @@ TreeView::TreeView(CurveEditorModel *model, QWidget *parent) setMouseTracking(true); setHeaderHidden(true); - model->setParent(this); setModel(model); auto expandItems = [this]() { expandAll(); }; @@ -54,7 +53,10 @@ TreeView::TreeView(CurveEditorModel *model, QWidget *parent) setSelectionBehavior(QAbstractItemView::SelectRows); setSelectionMode(QAbstractItemView::ExtendedSelection); - connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &TreeView::changeSelection); + connect(selectionModel(), + &QItemSelectionModel::selectionChanged, + this, + &TreeView::changeSelection); setStyle(model->style()); header()->setSectionResizeMode(0, QHeaderView::Stretch); @@ -83,6 +85,19 @@ void TreeView::setStyle(const CurveEditorStyle &style) delegate->setStyle(style); } +std::vector TreeView::selection() +{ + std::vector items; + for (auto &&index : selectionModel()->selectedRows(0)) { + if (index.isValid()) { + auto *treeItem = static_cast(index.internalPointer()); + if (auto *propertyItem = treeItem->asPropertyItem()) + items.push_back(new CurveItem(treeItem->id(), propertyItem->curve())); + } + } + return items; +} + void TreeView::changeCurve(unsigned int id, const AnimationCurve &curve) { if (auto *curveModel = qobject_cast(model())) @@ -98,8 +113,12 @@ void TreeView::changeSelection(const QItemSelection &selected, const QItemSelect for (auto index : selectedIndexes()) { if (index.isValid() && index.column() == 0) { auto *treeItem = static_cast(index.internalPointer()); - if (auto *propertyItem = treeItem->asPropertyItem()) - curves.push_back(new CurveItem(treeItem->id(), propertyItem->curve())); + if (auto *propertyItem = treeItem->asPropertyItem()) { + auto *citem = new CurveItem(treeItem->id(), propertyItem->curve()); + citem->setValueType(propertyItem->valueType()); + citem->setComponent(propertyItem->component()); + curves.push_back(citem); + } } } diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/treeview.h b/src/plugins/qmldesigner/components/curveeditor/detail/treeview.h index 9d3af647ad2..4b53601739a 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/treeview.h +++ b/src/plugins/qmldesigner/components/curveeditor/detail/treeview.h @@ -49,6 +49,8 @@ public: void setStyle(const CurveEditorStyle &style); + std::vector selection(); + protected: QSize sizeHint() const override; diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/utils.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/utils.cpp index 4933bcbe88f..4bc1cd285d3 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/utils.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/utils.cpp @@ -31,16 +31,6 @@ namespace DesignTools { -double clamp(double val, double lo, double hi) -{ - return val < lo ? lo : (val > hi ? hi : val); -} - -double lerp(double blend, double a, double b) -{ - return (1.0 - blend) * a + blend * b; -} - double scaleX(const QTransform &transform) { return transform.m11(); diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/utils.h b/src/plugins/qmldesigner/components/curveeditor/detail/utils.h index 77cbd2c7bf2..9142435ea41 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/utils.h +++ b/src/plugins/qmldesigner/components/curveeditor/detail/utils.h @@ -25,6 +25,8 @@ #pragma once +#include + QT_BEGIN_NAMESPACE class QColor; class QPalette; @@ -33,12 +35,10 @@ class QRectF; class QTransform; QT_END_NAMESPACE +#include + namespace DesignTools { -double clamp(double val, double lo, double hi); - -double lerp(double blend, double a, double b); - double scaleX(const QTransform &transform); double scaleY(const QTransform &transform); @@ -49,4 +49,30 @@ QRectF bbox(const QRectF &rect, const QTransform &transform); QPalette singleColorPalette(const QColor &color); +template +inline void freeClear(std::vector &vec) +{ + for (auto *&el : vec) + delete el; + vec.clear(); +} + +template +inline double clamp(const TV &val, const TC &lo, const TC &hi) +{ + return val < lo ? lo : (val > hi ? hi : val); +} + +template +inline T lerp(double blend, const T &a, const T &b) +{ + return (1.0 - blend) * a + blend * b; +} + +template +inline T reverseLerp(double blend, const T &a, const T &b) +{ + return (blend - b) / (a - b); +} + } // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/keyframe.cpp b/src/plugins/qmldesigner/components/curveeditor/keyframe.cpp index 8ff577c0a5f..4beea4b716b 100644 --- a/src/plugins/qmldesigner/components/curveeditor/keyframe.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/keyframe.cpp @@ -28,23 +28,49 @@ namespace DesignTools { Keyframe::Keyframe() - : m_position() + : m_interpolation(Interpolation::Undefined) + , m_position() , m_leftHandle() , m_rightHandle() + , m_data() {} Keyframe::Keyframe(const QPointF &position) - : m_position(position) + : m_interpolation(Interpolation::Linear) + , m_position(position) , m_leftHandle() , m_rightHandle() + , m_data() {} +Keyframe::Keyframe(const QPointF &position, const QVariant &data) + : m_interpolation(Interpolation::Undefined) + , m_position(position) + , m_leftHandle() + , m_rightHandle() + , m_data() +{ + setData(data); +} + Keyframe::Keyframe(const QPointF &position, const QPointF &leftHandle, const QPointF &rightHandle) - : m_position(position) + : m_interpolation(Interpolation::Bezier) + , m_position(position) , m_leftHandle(leftHandle) , m_rightHandle(rightHandle) + , m_data() {} +bool Keyframe::isValid() const +{ + return m_interpolation != Interpolation::Undefined; +} + +bool Keyframe::hasData() const +{ + return m_data.isValid(); +} + bool Keyframe::hasLeftHandle() const { return !m_leftHandle.isNull(); @@ -70,6 +96,21 @@ QPointF Keyframe::rightHandle() const return m_rightHandle; } +QVariant Keyframe::data() const +{ + return m_data; +} + +Keyframe::Interpolation Keyframe::interpolation() const +{ + return m_interpolation; +} + +void Keyframe::setInterpolation(Interpolation interpol) +{ + m_interpolation = interpol; +} + void Keyframe::setPosition(const QPointF &pos) { m_position = pos; @@ -85,4 +126,30 @@ void Keyframe::setRightHandle(const QPointF &pos) m_rightHandle = pos; } +void Keyframe::setData(const QVariant &data) +{ + if (data.type() == static_cast(QMetaType::QEasingCurve)) + m_interpolation = Interpolation::Easing; + + m_data = data; +} + +std::string toString(Keyframe::Interpolation interpol) +{ + switch (interpol) { + case Keyframe::Interpolation::Undefined: + return "Interpolation::Undefined"; + case Keyframe::Interpolation::Step: + return "Interpolation::Step"; + case Keyframe::Interpolation::Linear: + return "Interpolation::Linear"; + case Keyframe::Interpolation::Bezier: + return "Interpolation::Bezier"; + case Keyframe::Interpolation::Easing: + return "Interpolation::Easing"; + default: + return "Interpolation::Undefined"; + } +} + } // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/keyframe.h b/src/plugins/qmldesigner/components/curveeditor/keyframe.h index 5e6042531b0..be801e96f24 100644 --- a/src/plugins/qmldesigner/components/curveeditor/keyframe.h +++ b/src/plugins/qmldesigner/components/curveeditor/keyframe.h @@ -26,18 +26,34 @@ #pragma once #include +#include namespace DesignTools { class Keyframe { public: + enum class Interpolation + { + Undefined, + Step, + Linear, + Bezier, + Easing + }; + Keyframe(); Keyframe(const QPointF &position); + Keyframe(const QPointF &position, const QVariant& data); + Keyframe(const QPointF &position, const QPointF &leftHandle, const QPointF &rightHandle); + bool isValid() const; + + bool hasData() const; + bool hasLeftHandle() const; bool hasRightHandle() const; @@ -48,18 +64,32 @@ public: QPointF rightHandle() const; + QVariant data() const; + + Interpolation interpolation() const; + void setPosition(const QPointF &pos); void setLeftHandle(const QPointF &pos); void setRightHandle(const QPointF &pos); + void setData(const QVariant& data); + + void setInterpolation(Interpolation interpol); + private: + Interpolation m_interpolation = Interpolation::Undefined; + QPointF m_position; QPointF m_leftHandle; QPointF m_rightHandle; + + QVariant m_data; }; +std::string toString(Keyframe::Interpolation interpol); + } // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/treeitem.cpp b/src/plugins/qmldesigner/components/curveeditor/treeitem.cpp index beed419e547..dd145b38c02 100644 --- a/src/plugins/qmldesigner/components/curveeditor/treeitem.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/treeitem.cpp @@ -44,6 +44,7 @@ TreeItem::~TreeItem() m_parent = nullptr; qDeleteAll(m_children); + m_children.clear(); } QIcon TreeItem::icon() const @@ -66,6 +67,16 @@ unsigned int TreeItem::id() const return m_id; } +QString TreeItem::name() const +{ + return m_name; +} + +bool TreeItem::hasChildren() const +{ + return !m_children.empty(); +} + bool TreeItem::locked() const { return m_locked; @@ -185,7 +196,6 @@ void TreeItem::setPinned(bool pinned) m_pinned = pinned; } - NodeTreeItem::NodeTreeItem(const QString &name, const QIcon &icon) : TreeItem(name) , m_icon(icon) @@ -203,9 +213,12 @@ QIcon NodeTreeItem::icon() const return m_icon; } - -PropertyTreeItem::PropertyTreeItem(const QString &name, const AnimationCurve &curve) +PropertyTreeItem::PropertyTreeItem(const QString &name, + const AnimationCurve &curve, + const ValueType &type) : TreeItem(name) + , m_type(type) + , m_component(Component::Generic) , m_curve(curve) {} @@ -214,6 +227,27 @@ PropertyTreeItem *PropertyTreeItem::asPropertyItem() return this; } +const NodeTreeItem *PropertyTreeItem::parentNodeTreeItem() const +{ + TreeItem *p = parent(); + while (p) { + if (NodeTreeItem *ni = p->asNodeItem()) + return ni; + p = p->parent(); + } + return nullptr; +} + +ValueType PropertyTreeItem::valueType() const +{ + return m_type; +} + +PropertyTreeItem::Component PropertyTreeItem::component() const +{ + return m_component; +} + AnimationCurve PropertyTreeItem::curve() const { return m_curve; @@ -224,4 +258,9 @@ void PropertyTreeItem::setCurve(const AnimationCurve &curve) m_curve = curve; } +void PropertyTreeItem::setComponent(const Component &comp) +{ + m_component = comp; +} + } // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/treeitem.h b/src/plugins/qmldesigner/components/curveeditor/treeitem.h index 5b31dc2fc8c..0ae65d60aaf 100644 --- a/src/plugins/qmldesigner/components/curveeditor/treeitem.h +++ b/src/plugins/qmldesigner/components/curveeditor/treeitem.h @@ -58,6 +58,10 @@ public: unsigned int id() const; + QString name() const; + + bool hasChildren() const; + bool locked() const; bool pinned() const; @@ -102,7 +106,6 @@ protected: std::vector m_children; }; - class NodeTreeItem : public TreeItem { public: @@ -116,21 +119,42 @@ private: QIcon m_icon; }; +enum class ValueType { + Undefined, + Bool, + Integer, + Double, +}; class PropertyTreeItem : public TreeItem { public: - PropertyTreeItem(const QString &name, const AnimationCurve &curve); + enum class Component { Generic, R, G, B, A, X, Y, Z, W }; + +public: + PropertyTreeItem(const QString &name, const AnimationCurve &curve, const ValueType &type); PropertyTreeItem *asPropertyItem() override; + const NodeTreeItem *parentNodeTreeItem() const; + + ValueType valueType() const; + + Component component() const; + AnimationCurve curve() const; void setCurve(const AnimationCurve &curve); + void setComponent(const Component &comp); + private: using TreeItem::addChild; + ValueType m_type = ValueType::Undefined; + + Component m_component = Component::Generic; + AnimationCurve m_curve; }; diff --git a/src/plugins/qmldesigner/components/timelineeditor/animationcurvedialog.cpp b/src/plugins/qmldesigner/components/timelineeditor/animationcurvedialog.cpp new file mode 100644 index 00000000000..abc77781252 --- /dev/null +++ b/src/plugins/qmldesigner/components/timelineeditor/animationcurvedialog.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "animationcurvedialog.h" + +#include + +namespace QmlDesigner { + +AnimationCurveDialog::AnimationCurveDialog(QWidget *parent) + : QDialog(parent) + , m_editor(nullptr) +{ + setWindowFlag(Qt::WindowStaysOnTopHint, true); + setWindowFlag(Qt::WindowContextHelpButtonHint, false); +} + +AnimationCurveDialog::AnimationCurveDialog(DesignTools::CurveEditorModel *model, QWidget *parent) + : QDialog(parent) + , m_editor(nullptr) +{ + setWindowFlag(Qt::WindowStaysOnTopHint, true); + setWindowFlag(Qt::WindowContextHelpButtonHint, false); + setModel(model); +} + +void AnimationCurveDialog::setModel(DesignTools::CurveEditorModel *model) +{ + if (m_editor) + return; + + m_editor = new DesignTools::CurveEditor(model); + + auto *layout = new QVBoxLayout; + layout->addWidget(m_editor); + setLayout(layout); +} + +void AnimationCurveDialog::showEvent(QShowEvent *) +{ + m_editor->clearCanvas(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/animationcurvedialog.h b/src/plugins/qmldesigner/components/timelineeditor/animationcurvedialog.h new file mode 100644 index 00000000000..a567adec78b --- /dev/null +++ b/src/plugins/qmldesigner/components/timelineeditor/animationcurvedialog.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "curveeditor/curveeditor.h" + +#include + +namespace QmlDesigner { + +class AnimationCurveDialog : public QDialog +{ + Q_OBJECT + +public: + AnimationCurveDialog(QWidget *parent = nullptr); + + AnimationCurveDialog(DesignTools::CurveEditorModel *model, QWidget *parent = nullptr); + + void setModel(DesignTools::CurveEditorModel *model); + +protected: + void showEvent(QShowEvent *event) override; + +private: + DesignTools::CurveEditor *m_editor; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/animationcurveeditormodel.cpp b/src/plugins/qmldesigner/components/timelineeditor/animationcurveeditormodel.cpp new file mode 100644 index 00000000000..7b42255043f --- /dev/null +++ b/src/plugins/qmldesigner/components/timelineeditor/animationcurveeditormodel.cpp @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "animationcurveeditormodel.h" +#include "curveeditor/curveeditorstyle.h" +#include "curveeditor/treeitem.h" +#include "easingcurve.h" +#include "qmltimeline.h" + +#include +#include + +namespace QmlDesigner { + +AnimationCurveEditorModel::AnimationCurveEditorModel(double minTime, double maxTime) + : CurveEditorModel() + , m_minTime(minTime) + , m_maxTime(maxTime) +{} + +AnimationCurveEditorModel::~AnimationCurveEditorModel() {} + +double AnimationCurveEditorModel::minimumTime() const +{ + return m_minTime; +} + +double AnimationCurveEditorModel::maximumTime() const +{ + return m_maxTime; +} + +DesignTools::CurveEditorStyle AnimationCurveEditorModel::style() const +{ + // Pseudo auto generated. See: CurveEditorStyleDialog + DesignTools::CurveEditorStyle out; + out.backgroundBrush = QBrush(QColor(55, 55, 55)); + out.backgroundAlternateBrush = QBrush(QColor(0, 0, 50)); + out.fontColor = QColor(255, 255, 255); + out.gridColor = QColor(114, 116, 118); + out.canvasMargin = 15; + out.zoomInWidth = 99; + out.zoomInHeight = 99; + out.timeAxisHeight = 40; + out.timeOffsetLeft = 10; + out.timeOffsetRight = 10; + out.rangeBarColor = QColor(46, 47, 48); + out.rangeBarCapsColor = QColor(50, 50, 255); + out.valueAxisWidth = 60; + out.valueOffsetTop = 10; + out.valueOffsetBottom = 10; + out.handleStyle.size = 12; + out.handleStyle.lineWidth = 1; + out.handleStyle.color = QColor(255, 255, 255); + out.handleStyle.selectionColor = QColor(255, 255, 255); + out.keyframeStyle.size = 13; + out.keyframeStyle.color = QColor(172, 210, 255); + out.keyframeStyle.selectionColor = QColor(255, 255, 255); + out.curveStyle.width = 1; + out.curveStyle.color = QColor(0, 200, 0); + out.curveStyle.selectionColor = QColor(255, 255, 255); + return out; +} + +void AnimationCurveEditorModel::setTimeline(const QmlTimeline &timeline) +{ + m_minTime = timeline.startKeyframe(); + m_maxTime = timeline.endKeyframe(); + + std::vector items; + for (auto &&target : timeline.allTargets()) + if (DesignTools::TreeItem *item = createTopLevelItem(timeline, target)) + items.push_back(item); + + reset(items); +} + +void AnimationCurveEditorModel::setMinimumTime(double time) +{ + m_minTime = time; +} + +void AnimationCurveEditorModel::setMaximumTime(double time) +{ + m_maxTime = time; +} + +DesignTools::ValueType typeFrom(const QmlTimelineKeyframeGroup &group) +{ + if (group.valueType() == TypeName("double") || group.valueType() == TypeName("real")) + return DesignTools::ValueType::Double; + + if (group.valueType() == TypeName("bool")) + return DesignTools::ValueType::Bool; + + if (group.valueType() == TypeName("integer")) + return DesignTools::ValueType::Integer; + + return DesignTools::ValueType::Undefined; +} + +DesignTools::TreeItem *AnimationCurveEditorModel::createTopLevelItem(const QmlTimeline &timeline, + const ModelNode &node) +{ + if (!node.isValid()) + return nullptr; + + auto *nodeItem = new DesignTools::NodeTreeItem(node.id(), QIcon(":/ICON_INSTANCE")); + for (auto &&grp : timeline.keyframeGroupsForTarget(node)) { + if (grp.isValid()) { + DesignTools::AnimationCurve curve = createAnimationCurve(grp); + if (curve.isValid()) { + QString name = QString::fromUtf8(grp.propertyName()); + nodeItem->addChild(new DesignTools::PropertyTreeItem(name, curve, typeFrom(grp))); + } + } + } + + if (!nodeItem->hasChildren()) { + delete nodeItem; + nodeItem = nullptr; + } + + return nodeItem; +} + +DesignTools::AnimationCurve AnimationCurveEditorModel::createAnimationCurve( + const QmlTimelineKeyframeGroup &group) +{ + switch (typeFrom(group)) { + case DesignTools::ValueType::Bool: + return createDoubleCurve(group); + + case DesignTools::ValueType::Integer: + return createDoubleCurve(group); + + case DesignTools::ValueType::Double: + return createDoubleCurve(group); + default: + return DesignTools::AnimationCurve(); + } +} + +DesignTools::AnimationCurve AnimationCurveEditorModel::createDoubleCurve( + const QmlTimelineKeyframeGroup &group) +{ + std::vector keyframes; + for (auto &&frame : group.keyframePositions()) { + QVariant timeVariant = frame.variantProperty("frame").value(); + QVariant valueVariant = frame.variantProperty("value").value(); + + if (timeVariant.isValid() && valueVariant.isValid()) { + QPointF position(timeVariant.toDouble(), valueFromVariant(valueVariant)); + auto keyframe = DesignTools::Keyframe(position); + + if (frame.hasBindingProperty("easing.bezierCurve")) { + EasingCurve ecurve; + ecurve.fromString(frame.bindingProperty("easing.bezierCurve").expression()); + keyframe.setData(static_cast(ecurve)); + } + + keyframes.push_back(keyframe); + } + } + return DesignTools::AnimationCurve(keyframes); +} + +double AnimationCurveEditorModel::valueFromVariant(const QVariant &variant) +{ + return variant.toDouble(); +} + +void AnimationCurveEditorModel::reset(const std::vector &items) +{ + beginResetModel(); + + initialize(); + + unsigned int counter = 0; + for (auto *item : items) { + item->setId(++counter); + root()->addChild(item); + } + + endResetModel(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/animationcurveeditormodel.h b/src/plugins/qmldesigner/components/timelineeditor/animationcurveeditormodel.h new file mode 100644 index 00000000000..8bb1cc4e471 --- /dev/null +++ b/src/plugins/qmldesigner/components/timelineeditor/animationcurveeditormodel.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Design Tooling +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "curveeditor/curveeditormodel.h" +#include "curveeditor/treeitem.h" + +#include + +namespace QmlDesigner { + +class AnimationCurveEditorModel : public DesignTools::CurveEditorModel +{ + Q_OBJECT + +public: + AnimationCurveEditorModel(double minTime, double maxTime); + + ~AnimationCurveEditorModel() override; + + double minimumTime() const override; + + double maximumTime() const override; + + DesignTools::CurveEditorStyle style() const override; + + void setTimeline(const QmlTimeline &timeline); + + void setMinimumTime(double time); + + void setMaximumTime(double time); + +private: + DesignTools::TreeItem *createTopLevelItem(const QmlTimeline &timeline, const ModelNode &node); + + DesignTools::AnimationCurve createAnimationCurve(const QmlTimelineKeyframeGroup &group); + + DesignTools::AnimationCurve createDoubleCurve(const QmlTimelineKeyframeGroup &group); + + double valueFromVariant(const QVariant &variant); + + void reset(const std::vector &items); + + double m_minTime; + + double m_maxTime; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h b/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h index 6c012beec1e..c2a81373526 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h @@ -66,6 +66,7 @@ const char C_PLAY[] = "QmlDesigner.Play"; const char C_NEXT[] = "QmlDesigner.Next"; const char C_AUTO_KEYFRAME[] = "QmlDesigner.AutoKeyframe"; const char C_CURVE_PICKER[] = "QmlDesigner.CurvePicker"; +const char C_CURVE_EDITOR[] = "QmlDesigner.CurveEditor"; const char C_ZOOM_IN[] = "QmlDesigner.ZoomIn"; const char C_ZOOM_OUT[] = "QmlDesigner.ZoomOut"; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineeditor.pri b/src/plugins/qmldesigner/components/timelineeditor/timelineeditor.pri index 8001748fb00..184f61e4264 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineeditor.pri +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineeditor.pri @@ -36,7 +36,9 @@ SOURCES += \ easingcurve.cpp \ timelinesettingsmodel.cpp \ timelinetooldelegate.cpp \ - timelinecontrols.cpp + timelinecontrols.cpp \ + animationcurveeditormodel.cpp \ + animationcurvedialog.cpp HEADERS += \ timelineview.h \ @@ -69,7 +71,9 @@ HEADERS += \ canvasstyledialog.h \ easingcurve.h \ timelinesettingsmodel.h \ - timelinecontrols.h + timelinecontrols.h \ + animationcurveeditormodel.h \ + animationcurvedialog.h RESOURCES += \ timeline.qrc diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp index adc49a97b18..2b3560795d1 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp @@ -44,11 +44,13 @@ #include #include +#include #include #include #include #include -#include + +#include namespace QmlDesigner { @@ -99,7 +101,19 @@ QAction *createAction(const Core::Id &id, TimelineToolBar::TimelineToolBar(QWidget *parent) : QToolBar(parent) , m_grp() + , m_dialog() + , m_curveModel(new AnimationCurveEditorModel(0., 500.)) { + m_dialog.setModel(m_curveModel); + connect(m_curveModel, + &AnimationCurveEditorModel::currentFrameChanged, + this, + &TimelineToolBar::currentFrameChanged); + connect(m_curveModel, + &AnimationCurveEditorModel::curveChanged, + this, + &TimelineToolBar::curveChanged); + setContentsMargins(0, 0, 0, 0); createLeftControls(); createCenterControls(); @@ -146,6 +160,7 @@ void TimelineToolBar::setCurrentTimeline(const QmlTimeline &timeline) setStartFrame(timeline.startKeyframe()); setEndFrame(timeline.endKeyframe()); m_timelineLabel->setText(timeline.modelNode().id()); + m_curveModel->setTimeline(timeline); } else { m_timelineLabel->setText(""); } @@ -153,6 +168,8 @@ void TimelineToolBar::setCurrentTimeline(const QmlTimeline &timeline) void TimelineToolBar::setStartFrame(qreal frame) { + m_curveModel->setMinimumTime(frame); + auto text = QString::number(frame, 'f', 0); m_firstFrame->setText(text); setupCurrentFrameValidator(); @@ -160,12 +177,16 @@ void TimelineToolBar::setStartFrame(qreal frame) void TimelineToolBar::setCurrentFrame(qreal frame) { + m_curveModel->setCurrentFrame(std::round(frame)); + auto text = QString::number(frame, 'f', 0); m_currentFrame->setText(text); } void TimelineToolBar::setEndFrame(qreal frame) { + m_curveModel->setMaximumTime(frame); + auto text = QString::number(frame, 'f', 0); m_lastFrame->setText(text); setupCurrentFrameValidator(); @@ -190,6 +211,16 @@ void TimelineToolBar::removeTimeline(const QmlTimeline &timeline) setCurrentTimeline(QmlTimeline()); } +void TimelineToolBar::openAnimationCurveEditor() +{ + m_dialog.open(); +} + +void TimelineToolBar::updateCurve(DesignTools::PropertyTreeItem *item) +{ + DesignTools::AnimationCurve curve = item->curve(); +} + void TimelineToolBar::createLeftControls() { auto addActionToGroup = [&](QAction *action) { @@ -217,9 +248,19 @@ void TimelineToolBar::createLeftControls() QKeySequence(Qt::Key_S)); connect(settingsAction, &QAction::triggered, this, &TimelineToolBar::settingDialogClicked); - addActionToGroup(settingsAction); + auto *curveEditorAction = createAction(TimelineConstants::C_CURVE_EDITOR, + TimelineIcons::ANIMATION.icon(), + tr("Curve Editor"), + QKeySequence(Qt::Key_C)); + + connect(curveEditorAction, + &QAction::triggered, + this, + &TimelineToolBar::openAnimationCurveEditor); + addActionToGroup(curveEditorAction); + addWidgetToGroup(createSpacer()); m_timelineLabel = new QLabel(this); @@ -434,8 +475,9 @@ void TimelineToolBar::addSpacing(int width) void TimelineToolBar::setupCurrentFrameValidator() { - auto validator = static_cast(m_currentFrame->validator()); - const_cast(validator)->setRange(m_firstFrame->text().toInt(), m_lastFrame->text().toInt()); + auto validator = static_cast(m_currentFrame->validator()); + const_cast(validator)->setRange(m_firstFrame->text().toInt(), + m_lastFrame->text().toInt()); } void TimelineToolBar::resizeEvent(QResizeEvent *event) diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h index 43d42b83f92..e5fa7a79c93 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h @@ -25,6 +25,9 @@ #pragma once +#include "animationcurvedialog.h" +#include "animationcurveeditormodel.h" + #include QT_FORWARD_DECLARE_CLASS(QLabel) @@ -46,7 +49,7 @@ class TimelineToolBar : public QToolBar signals: void settingDialogClicked(); - //void addTimelineClicked(); + void curveEditorDialogClicked(); void openEasingCurveEditor(); @@ -64,6 +67,8 @@ signals: void currentFrameChanged(int value); void endFrameChanged(int value); + void curveChanged(DesignTools::PropertyTreeItem *item); + public: explicit TimelineToolBar(QWidget *parent = nullptr); @@ -83,6 +88,10 @@ public: void setActionEnabled(const QString &name, bool enabled); void removeTimeline(const QmlTimeline &timeline); + void openAnimationCurveEditor(); + + void updateCurve(DesignTools::PropertyTreeItem *item); + protected: void resizeEvent(QResizeEvent *event) override; @@ -95,6 +104,10 @@ private: QList m_grp; + AnimationCurveDialog m_dialog; + + AnimationCurveEditorModel *m_curveModel = nullptr; + QLabel *m_timelineLabel = nullptr; QLabel *m_stateLabel = nullptr; QSlider *m_scale = nullptr; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp index f39a17db2ff..c9d00f7eee6 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -50,7 +51,6 @@ #include #include #include -#include #include @@ -67,6 +67,7 @@ namespace QmlDesigner { TimelineView::TimelineView(QObject *parent) : AbstractView(parent) + , m_timelineWidget(nullptr) { EasingCurve::registerStreamOperators(); } @@ -106,7 +107,8 @@ void TimelineView::nodeAboutToBeRemoved(const ModelNode &removedNode) if (lastId != currentId) m_timelineWidget->setTimelineId(currentId); } else if (removedNode.parentProperty().isValid() - && QmlTimeline::isValidQmlTimeline(removedNode.parentProperty().parentModelNode())) { + && QmlTimeline::isValidQmlTimeline( + removedNode.parentProperty().parentModelNode())) { if (removedNode.hasBindingProperty("target")) { const ModelNode target = removedNode.bindingProperty("target").resolveToModelNode(); if (target.isValid()) { @@ -114,7 +116,8 @@ void TimelineView::nodeAboutToBeRemoved(const ModelNode &removedNode) if (timeline.hasKeyframeGroupForTarget(target)) QTimer::singleShot(0, [this, target, timeline]() { if (timeline.hasKeyframeGroupForTarget(target)) - m_timelineWidget->graphicsScene()->invalidateSectionForTarget(target); + m_timelineWidget->graphicsScene()->invalidateSectionForTarget( + target); else m_timelineWidget->graphicsScene()->invalidateScene(); }); @@ -186,6 +189,9 @@ void TimelineView::variantPropertiesChanged(const QList &proper if (QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(framesNode)) { QmlTimelineKeyframeGroup frames(framesNode); m_timelineWidget->graphicsScene()->invalidateKeyframesForTarget(frames.target()); + + QmlTimeline currentTimeline = m_timelineWidget->graphicsScene()->currentTimeline(); + m_timelineWidget->toolBar()->setCurrentTimeline(currentTimeline); } } } @@ -264,7 +270,7 @@ const QmlTimeline TimelineView::addNewTimeline() ModelNode timelineNode; - executeInTransaction("TimelineView::addNewTimeline", [=, &timelineNode](){ + executeInTransaction("TimelineView::addNewTimeline", [=, &timelineNode]() { bool hasTimelines = getTimelines().isEmpty(); timelineNode = createModelNode(timelineType, @@ -296,7 +302,7 @@ ModelNode TimelineView::addAnimation(QmlTimeline timeline) ModelNode animationNode; - executeInTransaction("TimelineView::addAnimation", [=, &animationNode](){ + executeInTransaction("TimelineView::addAnimation", [=, &animationNode]() { animationNode = createModelNode(animationType, metaInfo.majorVersion(), metaInfo.minorVersion()); @@ -380,22 +386,20 @@ void TimelineView::insertKeyframe(const ModelNode &target, const PropertyName &p QmlTimeline timeline = widget()->graphicsScene()->currentTimeline(); ModelNode targetNode = target; if (timeline.isValid() && targetNode.isValid() - && QmlObjectNode::isValidQmlObjectNode(targetNode)) { - executeInTransaction("TimelineView::insertKeyframe", [=, &timeline, &targetNode](){ - + && QmlObjectNode::isValidQmlObjectNode(targetNode)) { + executeInTransaction("TimelineView::insertKeyframe", [=, &timeline, &targetNode]() { targetNode.validId(); QmlTimelineKeyframeGroup timelineFrames( - timeline.keyframeGroup(targetNode, propertyName)); + timeline.keyframeGroup(targetNode, propertyName)); QTC_ASSERT(timelineFrames.isValid(), return ); const qreal frame - = timeline.modelNode().auxiliaryData("currentFrame@NodeInstance").toReal(); + = timeline.modelNode().auxiliaryData("currentFrame@NodeInstance").toReal(); const QVariant value = QmlObjectNode(targetNode).instanceValue(propertyName); timelineFrames.setValue(value, frame); - }); } } @@ -408,7 +412,8 @@ QList TimelineView::getTimelines() const return timelines; for (const ModelNode &modelNode : allModelNodes()) { - if (QmlTimeline::isValidQmlTimeline(modelNode) && !modelNode.hasAuxiliaryData("removed@Internal")) { + if (QmlTimeline::isValidQmlTimeline(modelNode) + && !modelNode.hasAuxiliaryData("removed@Internal")) { timelines.append(modelNode); } } @@ -482,11 +487,10 @@ void TimelineView::registerActions() SelectionContextPredicate timelineEnabled = [this](const SelectionContext &context) { return context.singleNodeIsSelected() - && widget()->graphicsScene()->currentTimeline().isValid(); + && widget()->graphicsScene()->currentTimeline().isValid(); }; - SelectionContextPredicate timelineHasKeyframes = - [this](const SelectionContext &context) { + SelectionContextPredicate timelineHasKeyframes = [this](const SelectionContext &context) { auto timeline = widget()->graphicsScene()->currentTimeline(); return !timeline.keyframeGroupsForTarget(context.currentSingleSelectedNode()).isEmpty(); }; @@ -528,44 +532,44 @@ void TimelineView::registerActions() &SelectionContextFunctors::always)); actionManager.addDesignerAction( - new ModelNodeContextMenuAction("commandId timeline delete", - TimelineConstants::timelineDeleteKeyframesDisplayName, - {}, - TimelineConstants::timelineCategory, - QKeySequence(), - 160, - deleteKeyframes, - timelineHasKeyframes)); + new ModelNodeContextMenuAction("commandId timeline delete", + TimelineConstants::timelineDeleteKeyframesDisplayName, + {}, + TimelineConstants::timelineCategory, + QKeySequence(), + 160, + deleteKeyframes, + timelineHasKeyframes)); actionManager.addDesignerAction( - new ModelNodeContextMenuAction("commandId timeline insert", - TimelineConstants::timelineInsertKeyframesDisplayName, - {}, - TimelineConstants::timelineCategory, - QKeySequence(), - 140, - insertKeyframes, - timelineHasKeyframes)); + new ModelNodeContextMenuAction("commandId timeline insert", + TimelineConstants::timelineInsertKeyframesDisplayName, + {}, + TimelineConstants::timelineCategory, + QKeySequence(), + 140, + insertKeyframes, + timelineHasKeyframes)); actionManager.addDesignerAction( - new ModelNodeContextMenuAction("commandId timeline copy", - TimelineConstants::timelineCopyKeyframesDisplayName, - {}, - TimelineConstants::timelineCategory, - QKeySequence(), - 120, - copyKeyframes, - timelineHasKeyframes)); + new ModelNodeContextMenuAction("commandId timeline copy", + TimelineConstants::timelineCopyKeyframesDisplayName, + {}, + TimelineConstants::timelineCategory, + QKeySequence(), + 120, + copyKeyframes, + timelineHasKeyframes)); actionManager.addDesignerAction( - new ModelNodeContextMenuAction("commandId timeline paste", - TimelineConstants::timelinePasteKeyframesDisplayName, - {}, - TimelineConstants::timelineCategory, - QKeySequence(), - 100, - pasteKeyframes, - timelineHasClipboard)); + new ModelNodeContextMenuAction("commandId timeline paste", + TimelineConstants::timelinePasteKeyframesDisplayName, + {}, + TimelineConstants::timelineCategory, + QKeySequence(), + 100, + pasteKeyframes, + timelineHasClipboard)); } TimelineWidget *TimelineView::createWidget() diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineview.h b/src/plugins/qmldesigner/components/timelineeditor/timelineview.h index 057ff3047b2..bf5d2694c5c 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineview.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineview.h @@ -25,6 +25,10 @@ #pragma once +#include "animationcurvedialog.h" +#include "animationcurveeditormodel.h" +#include "treeitem.h" + #include #include diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp index 8a58eb7dcf5..169ac5f2f00 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp @@ -24,6 +24,9 @@ ****************************************************************************/ #include "timelinewidget.h" +#include "bindingproperty.h" +#include "curvesegment.h" +#include "easingcurve.h" #include "easingcurvedialog.h" #include "timelineconstants.h" #include "timelinegraphicsscene.h" @@ -203,6 +206,8 @@ void TimelineWidget::connectToolbar() connect(graphicsScene(), &TimelineGraphicsScene::scroll, this, &TimelineWidget::scroll); + connect(m_toolbar, &TimelineToolBar::curveChanged, this, &TimelineWidget::updateAnimationCurve); + auto setRulerScaling = [this](int val) { m_graphicsScene->setRulerScaling(val); }; connect(m_toolbar, &TimelineToolBar::scaleFactorChanged, setRulerScaling); @@ -233,10 +238,7 @@ void TimelineWidget::connectToolbar() auto setEndFrame = [this](int end) { graphicsScene()->setEndFrame(end); }; connect(m_toolbar, &TimelineToolBar::endFrameChanged, setEndFrame); - - connect(m_toolbar, &TimelineToolBar::recordToggled, - this, - &TimelineWidget::setTimelineRecording); + connect(m_toolbar, &TimelineToolBar::recordToggled, this, &TimelineWidget::setTimelineRecording); connect(m_toolbar, &TimelineToolBar::openEasingCurveEditor, @@ -283,6 +285,79 @@ void TimelineWidget::scroll(const TimelineUtils::Side &side) m_scrollbar->setValue(m_scrollbar->value() + m_scrollbar->singleStep()); } +ModelNode getTargetNode(DesignTools::PropertyTreeItem *item, const QmlTimeline &timeline) +{ + if (const DesignTools::NodeTreeItem *nodeItem = item->parentNodeTreeItem()) { + QString targetId = nodeItem->name(); + if (timeline.isValid()) { + for (auto &&target : timeline.allTargets()) { + if (target.displayName() == targetId) + return target; + } + } + } + return ModelNode(); +} + +QmlTimelineKeyframeGroup timelineKeyframeGroup(QmlTimeline &timeline, + DesignTools::PropertyTreeItem *item) +{ + ModelNode node = getTargetNode(item, timeline); + if (node.isValid()) + return timeline.keyframeGroup(node, item->name().toLatin1()); + + return QmlTimelineKeyframeGroup(); +} + +void attachEasingCurve(double frame, + const QEasingCurve &curve, + const QmlTimelineKeyframeGroup &group) +{ + ModelNode frameNode = group.keyframe(frame); + if (frameNode.isValid()) { + auto expression = EasingCurve(curve).toString(); + frameNode.bindingProperty("easing.bezierCurve").setExpression(expression); + } +} + +void TimelineWidget::updateAnimationCurve(DesignTools::PropertyTreeItem *item) +{ + QmlTimeline currentTimeline = graphicsScene()->currentTimeline(); + QmlTimelineKeyframeGroup group = timelineKeyframeGroup(currentTimeline, item); + + if (group.isValid()) { + auto replaceKeyframes = [&group, currentTimeline, item]() { + for (auto frame : group.keyframes()) + frame.destroy(); + + DesignTools::Keyframe previous; + for (auto &&frame : item->curve().keyframes()) { + QPointF pos = frame.position(); + group.setValue(QVariant(pos.y()), pos.x()); + + if (previous.isValid()) { + if (frame.interpolation() == DesignTools::Keyframe::Interpolation::Bezier) { + DesignTools::CurveSegment segment(previous, frame); + attachEasingCurve(pos.x(), segment.easingCurve(), group); + } else if (frame.interpolation() + == DesignTools::Keyframe::Interpolation::Easing) { + QVariant data = frame.data(); + if (data.type() == static_cast(QMetaType::QEasingCurve)) + attachEasingCurve(pos.x(), data.value(), group); + } else if (frame.interpolation() == DesignTools::Keyframe::Interpolation::Step) { + // Warning: Keyframe::Interpolation::Step not yet implemented + } + } + + previous = frame; + } + }; + + timelineView()->executeInTransaction("TimelineWidget::handleKeyframeReplacement", + replaceKeyframes); + } +} + void TimelineWidget::selectionChanged() { if (graphicsScene()->hasSelection()) diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h index 3fd299a88a7..681181dbe91 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h @@ -25,6 +25,7 @@ #pragma once +#include "animationcurveeditormodel.h" #include "timelineutils.h" #include @@ -77,6 +78,8 @@ public slots: void changeScaleFactor(int factor); void scroll(const TimelineUtils::Side &side); + void updateAnimationCurve(DesignTools::PropertyTreeItem *item); + protected: void showEvent(QShowEvent *event) override; void resizeEvent(QResizeEvent *event) override; diff --git a/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h b/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h index f5d708d8563..c37be6934b6 100644 --- a/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h +++ b/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h @@ -25,9 +25,9 @@ #pragma once -#include -#include "qmlmodelnodefacade.h" #include "qmlchangeset.h" +#include "qmlmodelnodefacade.h" +#include namespace QmlDesigner { @@ -37,7 +37,6 @@ class QmlTimeline; class QMLDESIGNERCORE_EXPORT QmlTimelineKeyframeGroup : public QmlModelNodeFacade { - public: QmlTimelineKeyframeGroup(); QmlTimelineKeyframeGroup(const ModelNode &modelNode); @@ -64,6 +63,10 @@ public: qreal minActualKeyframe() const; qreal maxActualKeyframe() const; + ModelNode keyframe(qreal position) const; + + const QList keyframes() const; + const QList keyframePositions() const; static bool isValidKeyframe(const ModelNode &node); @@ -83,4 +86,4 @@ public: QmlTimeline timeline() const; }; -} //QmlDesigner +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp b/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp index 8a6defa4694..11474d91711 100644 --- a/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp @@ -25,12 +25,12 @@ #include "qmltimelinekeyframegroup.h" #include "abstractview.h" -#include -#include -#include -#include #include "bindingproperty.h" #include "qmlitemnode.h" +#include +#include +#include +#include #include @@ -41,10 +41,9 @@ namespace QmlDesigner { QmlTimelineKeyframeGroup::QmlTimelineKeyframeGroup() = default; -QmlTimelineKeyframeGroup::QmlTimelineKeyframeGroup(const ModelNode &modelNode) : QmlModelNodeFacade(modelNode) -{ - -} +QmlTimelineKeyframeGroup::QmlTimelineKeyframeGroup(const ModelNode &modelNode) + : QmlModelNodeFacade(modelNode) +{} bool QmlTimelineKeyframeGroup::isValid() const { @@ -53,8 +52,8 @@ bool QmlTimelineKeyframeGroup::isValid() const bool QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(const ModelNode &modelNode) { - return modelNode.isValid() && modelNode.metaInfo().isValid() - && modelNode.metaInfo().isSubclassOf("QtQuick.Timeline.KeyframeGroup"); + return modelNode.isValid() && modelNode.metaInfo().isValid() + && modelNode.metaInfo().isSubclassOf("QtQuick.Timeline.KeyframeGroup"); } void QmlTimelineKeyframeGroup::destroy() @@ -73,12 +72,11 @@ ModelNode QmlTimelineKeyframeGroup::target() const void QmlTimelineKeyframeGroup::setTarget(const ModelNode &target) { - QTC_ASSERT(isValid(), return); + QTC_ASSERT(isValid(), return ); modelNode().bindingProperty("target").setExpression(target.id()); } - PropertyName QmlTimelineKeyframeGroup::propertyName() const { QTC_ASSERT(isValid(), return {}); @@ -88,7 +86,7 @@ PropertyName QmlTimelineKeyframeGroup::propertyName() const void QmlTimelineKeyframeGroup::setPropertyName(const PropertyName &propertyName) { - QTC_ASSERT(isValid(), return); + QTC_ASSERT(isValid(), return ); modelNode().variantProperty("property").setValue(QString::fromUtf8(propertyName)); } @@ -135,7 +133,7 @@ bool QmlTimelineKeyframeGroup::isRecording() const void QmlTimelineKeyframeGroup::toogleRecording(bool record) const { - QTC_ASSERT(isValid(), return); + QTC_ASSERT(isValid(), return ); if (!record) { if (isRecording()) @@ -157,7 +155,7 @@ QmlTimeline QmlTimelineKeyframeGroup::timeline() const void QmlTimelineKeyframeGroup::setValue(const QVariant &value, qreal currentFrame) { - QTC_ASSERT(isValid(), return); + QTC_ASSERT(isValid(), return ); for (const ModelNode &childNode : modelNode().defaultNodeListProperty().toModelNodeList()) { if (qFuzzyCompare(childNode.variantProperty("frame").value().toReal(), currentFrame)) { @@ -166,10 +164,14 @@ void QmlTimelineKeyframeGroup::setValue(const QVariant &value, qreal currentFram } } - const QList > propertyPairList{{PropertyName("frame"), QVariant(currentFrame)}, - {PropertyName("value"), value}}; + const QList> propertyPairList{{PropertyName("frame"), + QVariant(currentFrame)}, + {PropertyName("value"), value}}; - ModelNode frame = modelNode().view()->createModelNode("QtQuick.Timeline.Keyframe", 1, 0, propertyPairList); + ModelNode frame = modelNode().view()->createModelNode("QtQuick.Timeline.Keyframe", + 1, + 0, + propertyPairList); NodeListProperty nodeListProperty = modelNode().defaultNodeListProperty(); const int sourceIndex = nodeListProperty.count(); @@ -215,6 +217,16 @@ bool QmlTimelineKeyframeGroup::hasKeyframe(qreal frame) return false; } +ModelNode QmlTimelineKeyframeGroup::keyframe(qreal frame) const +{ + for (const ModelNode &childNode : modelNode().defaultNodeListProperty().toModelNodeList()) { + if (qFuzzyCompare(childNode.variantProperty("frame").value().toReal(), frame)) + return childNode; + } + + return ModelNode(); +} + qreal QmlTimelineKeyframeGroup::minActualKeyframe() const { QTC_ASSERT(isValid(), return -1); @@ -243,6 +255,11 @@ qreal QmlTimelineKeyframeGroup::maxActualKeyframe() const return max; } +const QList QmlTimelineKeyframeGroup::keyframes() const +{ + return modelNode().defaultNodeListProperty().toModelNodeList(); +} + const QList QmlTimelineKeyframeGroup::keyframePositions() const { QList returnValues; @@ -257,14 +274,13 @@ const QList QmlTimelineKeyframeGroup::keyframePositions() const bool QmlTimelineKeyframeGroup::isValidKeyframe(const ModelNode &node) { - return isValidQmlModelNodeFacade(node) - && node.metaInfo().isValid() - && node.metaInfo().isSubclassOf("QtQuick.Timeline.Keyframe"); + return isValidQmlModelNodeFacade(node) && node.metaInfo().isValid() + && node.metaInfo().isSubclassOf("QtQuick.Timeline.Keyframe"); } bool QmlTimelineKeyframeGroup::checkKeyframesType(const ModelNode &node) { - return node.isValid() && node.type() == "QtQuick.Timeline.KeyframeGroup"; + return node.isValid() && node.type() == "QtQuick.Timeline.KeyframeGroup"; } QmlTimelineKeyframeGroup QmlTimelineKeyframeGroup::keyframeGroupForKeyframe(const ModelNode &node) @@ -297,4 +313,4 @@ void QmlTimelineKeyframeGroup::scaleAllKeyframes(qreal factor) } } -} // QmlDesigner +} // namespace QmlDesigner From 6e241d6eac6e5c42f3bdbb9e16f443d80e9edeb2 Mon Sep 17 00:00:00 2001 From: Knud Dollereder Date: Thu, 8 Aug 2019 17:03:42 +0200 Subject: [PATCH 07/10] Improve scaling of the graphicsscene in y direction Change-Id: I2e4f07cb39222094fdbb26e45d4385510c8992d4 Reviewed-by: Thomas Hartmann --- .../components/curveeditor/detail/graphicsview.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp index 49321807d74..954e0401c4a 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp @@ -371,17 +371,13 @@ void GraphicsView::applyZoom(double x, double y, const QPoint &pivot) double minTime = minimumTime(); double maxTime = maximumTime(); - double minValue = minimumValue(); - double maxValue = maximumValue(); - - QRectF canvas = canvasRect(); double xZoomedOut = canvas.width() / (maxTime - minTime); double xZoomedIn = m_style.zoomInWidth; double scaleX = lerp(clamp(m_zoomX, 0.0, 1.0), xZoomedOut, xZoomedIn); - double yZoomedOut = canvas.height() / (maxValue - minValue); + double yZoomedOut = canvas.height() / maximumValue(); double yZoomedIn = m_style.zoomInHeight; double scaleY = lerp(clamp(m_zoomY, 0.0, 1.0), -yZoomedOut, -yZoomedIn); From 61046e848a51c404f67e8f27b6b9d7b9e42ccb1d Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Thu, 8 Aug 2019 18:19:55 +0200 Subject: [PATCH 08/10] QmlDesigner: Fix qbs build Change-Id: I3b114db5f51f47a2e5635858f5587eefec7393c0 Reviewed-by: hjk --- src/plugins/qmldesigner/qmldesignerplugin.qbs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 2db56446f3c..30e9273ef2c 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -638,6 +638,8 @@ Project { "curveeditor/curveeditormodel.cpp", "curveeditor/curveeditormodel.h", "curveeditor/curveeditorstyle.h", + "curveeditor/curvesegment.cpp", + "curveeditor/curvesegment.h", "curveeditor/keyframe.cpp", "curveeditor/keyframe.h", "curveeditor/treeitem.cpp", @@ -648,8 +650,6 @@ Project { "curveeditor/detail/curveeditorstyledialog.h", "curveeditor/detail/curveitem.cpp", "curveeditor/detail/curveitem.h", - "curveeditor/detail/curvesegment.cpp", - "curveeditor/detail/curvesegment.h", "curveeditor/detail/graphicsscene.cpp", "curveeditor/detail/graphicsscene.h", "curveeditor/detail/graphicsview.cpp", From ca32a4712249054fcea9f7d19c5a9907e5344877 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Mon, 5 Aug 2019 14:03:33 +0200 Subject: [PATCH 09/10] Squish: Use parameters for mouseClick Another partial revert for e8727fcae25d96. Change-Id: Ieba9b7ea75421858698e0da0133278408e2b5ee1 Reviewed-by: Robert Loehning --- tests/system/suite_tools/tst_designer_goto_slot/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/suite_tools/tst_designer_goto_slot/test.py b/tests/system/suite_tools/tst_designer_goto_slot/test.py index f07c0faa8e6..11a1b4d6719 100644 --- a/tests/system/suite_tools/tst_designer_goto_slot/test.py +++ b/tests/system/suite_tools/tst_designer_goto_slot/test.py @@ -52,7 +52,7 @@ def main(): activateItem(waitForObjectItem("{type='QMenu' unnamed='1' visible='1'}", "Go to slot...")) signalWidgetObject = waitForObject(":Select signal.signalList_QTreeView") signalName = con[1] + "." + con[2] - mouseClick(waitForObjectItem(signalWidgetObject, signalName)) + mouseClick(waitForObjectItem(signalWidgetObject, signalName), 5, 5, 0, Qt.LeftButton) clickButton(waitForObject(":Go to slot.OK_QPushButton")) editor = waitForObject(":Qt Creator_CppEditor::Internal::CPPEditorWidget") type(editor, "") From 98d454d58b6c6eb129032f8784384daf1267c471 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Fri, 9 Aug 2019 09:03:14 +0200 Subject: [PATCH 10/10] Fix CMake build Change-Id: Ib0f90b9051c631751a1181dc30e58affd90feffb Reviewed-by: Christian Stenger --- src/plugins/qmldesigner/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 8801aed4a9a..1786026e731 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -537,6 +537,8 @@ extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/timelineeditor SOURCES + animationcurvedialog.cpp animationcurvedialog.h + animationcurveeditormodel.cpp animationcurveeditormodel.h canvas.cpp canvas.h canvasstyledialog.cpp canvasstyledialog.h easingcurve.cpp easingcurve.h @@ -580,12 +582,12 @@ extend_qtc_plugin(QmlDesigner curveeditor.cpp curveeditor.h curveeditormodel.cpp curveeditormodel.h curveeditorstyle.h + curvesegment.cpp curvesegment.h keyframe.cpp keyframe.h treeitem.cpp treeitem.h detail/colorcontrol.cpp detail/colorcontrol.h detail/curveeditorstyledialog.cpp detail/curveeditorstyledialog.h detail/curveitem.cpp detail/curveitem.h - detail/curvesegment.cpp detail/curvesegment.h detail/graphicsscene.cpp detail/graphicsscene.h detail/graphicsview.cpp detail/graphicsview.h detail/handleitem.cpp detail/handleitem.h