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/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 diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 2a26bb8e7bd..1786026e731 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 @@ -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 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..954e0401c4a 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)) { @@ -352,21 +371,17 @@ 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); m_transform = QTransform::fromScale(scaleX, scaleY); - m_scene.setComponentTransform(m_transform); QRectF sr = m_scene.sceneRect().adjusted( @@ -385,13 +400,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 1499763a538..3dff7bf7842 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 3bff0a348e0..492bcd66272 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 879ba6e9349..c8b6057d875 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 3e437bda968..871c799247d 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 4daa6d3e3e4..061f5199938 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 6bc0ce57bfa..18782282c21 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/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/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 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()) { 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..30e9273ef2c 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", ] } @@ -639,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", @@ -649,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", diff --git a/src/plugins/qtsupport/qtoutputformatter.cpp b/src/plugins/qtsupport/qtoutputformatter.cpp index 8fcee4b64d6..8c7c5c1cb12 100644 --- a/src/plugins/qtsupport/qtoutputformatter.cpp +++ b/src/plugins/qtsupport/qtoutputformatter.cpp @@ -423,6 +423,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" @@ -434,6 +439,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; } 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", ] } 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, "")