forked from qt-creator/qt-creator
Connect CurveEditor edits to the timeline module
Change-Id: Ic00e0840da34bdbb8627b2fe2d8546a867b24966 Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -24,40 +24,69 @@
|
||||
****************************************************************************/
|
||||
|
||||
#include "animationcurve.h"
|
||||
#include "detail/curvesegment.h"
|
||||
#include "curvesegment.h"
|
||||
#include "detail/utils.h"
|
||||
|
||||
#include <QEasingCurve>
|
||||
#include <QLineF>
|
||||
#include <QPainterPath>
|
||||
|
||||
namespace DesignTools {
|
||||
|
||||
AnimationCurve::AnimationCurve()
|
||||
: m_frames()
|
||||
: m_fromData(false)
|
||||
, m_minY(std::numeric_limits<double>::max())
|
||||
, m_maxY(std::numeric_limits<double>::lowest())
|
||||
, m_frames()
|
||||
{}
|
||||
|
||||
AnimationCurve::AnimationCurve(const std::vector<Keyframe> &frames)
|
||||
: m_frames(frames)
|
||||
: m_fromData(false)
|
||||
, m_minY(std::numeric_limits<double>::max())
|
||||
, m_maxY(std::numeric_limits<double>::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<double>::max())
|
||||
, m_maxY(std::numeric_limits<double>::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<QPointF> 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<CurveSegment> AnimationCurve::segments() const
|
||||
{
|
||||
if (m_frames.empty())
|
||||
return {};
|
||||
|
||||
std::vector<CurveSegment> 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<Keyframe> AnimationCurve::keyframes() const
|
||||
{
|
||||
return m_frames;
|
||||
@@ -100,19 +198,10 @@ std::vector<QPointF> AnimationCurve::extrema() const
|
||||
{
|
||||
std::vector<QPointF> 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<double> AnimationCurve::xForY(double y, uint segment) const
|
||||
return std::vector<double>();
|
||||
}
|
||||
|
||||
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<Keyframe> 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<Keyframe, 3> &&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<double>::max();
|
||||
m_maxY = std::numeric_limits<double>::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.
|
||||
|
||||
@@ -27,10 +27,16 @@
|
||||
|
||||
#include "keyframe.h"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
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<Keyframe> &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<CurveSegment> segments() const;
|
||||
|
||||
QPainterPath simplePath() const;
|
||||
|
||||
QPainterPath intersectionPath() const;
|
||||
|
||||
std::vector<Keyframe> keyframes() const;
|
||||
|
||||
std::vector<QPointF> extrema() const;
|
||||
@@ -56,14 +74,22 @@ public:
|
||||
|
||||
std::vector<double> 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<Keyframe> m_frames;
|
||||
void analyze();
|
||||
|
||||
bool m_fromData;
|
||||
|
||||
double m_minY;
|
||||
|
||||
double m_maxY;
|
||||
|
||||
std::vector<Keyframe> m_frames;
|
||||
};
|
||||
|
||||
} // End namespace DesignTools.
|
||||
|
||||
@@ -29,8 +29,11 @@
|
||||
#include "detail/graphicsview.h"
|
||||
#include "detail/treeview.h"
|
||||
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QSplitter>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
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.
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QToolBar>
|
||||
#include <QWidget>
|
||||
|
||||
namespace DesignTools {
|
||||
@@ -44,7 +45,11 @@ public:
|
||||
|
||||
void zoomY(double zoom);
|
||||
|
||||
void clearCanvas();
|
||||
|
||||
private:
|
||||
QToolBar *createToolBar();
|
||||
|
||||
TreeView *m_tree;
|
||||
|
||||
GraphicsView *m_view;
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
500
src/plugins/qmldesigner/components/curveeditor/curvesegment.cpp
Normal file
500
src/plugins/qmldesigner/components/curveeditor/curvesegment.cpp
Normal file
@@ -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 <utils/qtcassert.h>
|
||||
#include <QEasingCurve>
|
||||
#include <QPainterPath>
|
||||
#include <qmath.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace DesignTools {
|
||||
|
||||
class CubicPolynomial
|
||||
{
|
||||
public:
|
||||
CubicPolynomial(double p0, double p1, double p2, double p3);
|
||||
|
||||
std::vector<double> extrema() const;
|
||||
|
||||
std::vector<double> 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<double> CubicPolynomial::extrema() const
|
||||
{
|
||||
std::vector<double> 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<double> CubicPolynomial::roots() const
|
||||
{
|
||||
std::vector<double> 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<int>(QMetaType::QEasingCurve)) {
|
||||
QVector<QPointF> points = data.value<QEasingCurve>().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<QPointF> CurveSegment::extrema() const
|
||||
{
|
||||
std::vector<QPointF> 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<double> 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<double> out;
|
||||
for (double t : polynomial.roots()) {
|
||||
if (t >= 0.0 && t <= 1.0)
|
||||
out.push_back(t);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<double> 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<double> out;
|
||||
for (double t : polynomial.roots()) {
|
||||
if (t >= 0.0 && t <= 1.0)
|
||||
out.push_back(t);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<double> CurveSegment::yForX(double x) const
|
||||
{
|
||||
std::vector<double> 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<double> CurveSegment::xForY(double y) const
|
||||
{
|
||||
std::vector<double> 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<Keyframe, 3> CurveSegment::splitAt(double time)
|
||||
{
|
||||
std::array<Keyframe, 3> 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.
|
||||
@@ -27,10 +27,13 @@
|
||||
|
||||
#include "keyframe.h"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
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<QPointF> extrema() const;
|
||||
|
||||
std::vector<double> tForX(double x) const;
|
||||
|
||||
std::vector<double> tForY(double y) const;
|
||||
|
||||
std::vector<double> yForX(double x) const;
|
||||
|
||||
std::vector<double> xForY(double y) const;
|
||||
|
||||
std::array<Keyframe, 3> 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;
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
#include "keyframeitem.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QEasingCurve>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
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<Keyframe> out;
|
||||
out.reserve(m_keyframes.size());
|
||||
for (auto item : m_keyframes)
|
||||
out.push_back(item->keyframe());
|
||||
std::vector<Keyframe> 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<AnimationCurve> 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<AnimationCurve> CurveItem::curves() const
|
||||
{
|
||||
std::vector<AnimationCurve> out;
|
||||
|
||||
std::vector<Keyframe> 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<QEasingCurve>(),
|
||||
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.
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "curveeditorstyle.h"
|
||||
#include "curvesegment.h"
|
||||
#include "keyframe.h"
|
||||
#include "selectableitem.h"
|
||||
#include "treeitem.h"
|
||||
|
||||
#include <QGraphicsObject>
|
||||
|
||||
@@ -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<AnimationCurve> 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<KeyframeItem *> m_keyframes;
|
||||
@@ -89,10 +122,6 @@ private:
|
||||
bool m_underMouse;
|
||||
|
||||
bool m_itemDirty;
|
||||
|
||||
mutable bool m_pathDirty;
|
||||
|
||||
mutable QPainterPath m_path;
|
||||
};
|
||||
|
||||
} // End namespace DesignTools.
|
||||
|
||||
@@ -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 <qmath.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace DesignTools {
|
||||
|
||||
class CubicPolynomial
|
||||
{
|
||||
public:
|
||||
CubicPolynomial(double p0, double p1, double p2, double p3);
|
||||
|
||||
std::vector<double> extrema() const;
|
||||
|
||||
std::vector<double> 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<double> CubicPolynomial::extrema() const
|
||||
{
|
||||
std::vector<double> 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<double> CubicPolynomial::roots() const
|
||||
{
|
||||
std::vector<double> 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<QPointF> CurveSegment::extrema() const
|
||||
{
|
||||
std::vector<QPointF> 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<double> CurveSegment::yForX(double x) const
|
||||
{
|
||||
std::vector<double> 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<double> CurveSegment::xForY(double y) const
|
||||
{
|
||||
std::vector<double> 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.
|
||||
@@ -141,6 +141,10 @@ void GraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
|
||||
const auto itemList = items();
|
||||
for (auto *item : itemList) {
|
||||
if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(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<CurveItem *>(item)) {
|
||||
auto curve = curveItem->curve();
|
||||
auto curve = curveItem->resolvedCurve();
|
||||
if (min.x() > curve.minimumTime())
|
||||
min.rx() = curve.minimumTime();
|
||||
|
||||
|
||||
@@ -209,6 +209,18 @@ void GraphicsView::reset(const std::vector<CurveItem *> &items)
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void GraphicsView::setInterpolation(Keyframe::Interpolation interpol)
|
||||
{
|
||||
const auto itemList = items();
|
||||
for (auto *item : itemList) {
|
||||
if (auto *citem = qgraphicsitem_cast<CurveItem *>(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<Qt::Key>(event->key()));
|
||||
if (shortcut == m_style.shortcuts.frameAll)
|
||||
applyZoom(0.0, 0.0);
|
||||
else if (shortcut == m_style.shortcuts.deleteKeyframe)
|
||||
deleteSelectedKeyframes();
|
||||
}
|
||||
|
||||
void GraphicsView::mousePressEvent(QMouseEvent *event)
|
||||
@@ -228,6 +242,11 @@ void GraphicsView::mousePressEvent(QMouseEvent *event)
|
||||
return;
|
||||
|
||||
Shortcut shortcut(event);
|
||||
if (shortcut == m_style.shortcuts.insertKeyframe) {
|
||||
insertKeyframe(globalToRaster(event->globalPos()).x());
|
||||
return;
|
||||
}
|
||||
|
||||
if (shortcut == Shortcut(Qt::LeftButton)) {
|
||||
QPointF pos = mapToScene(event->pos());
|
||||
if (timeScaleRect().contains(pos)) {
|
||||
@@ -355,6 +374,7 @@ void GraphicsView::applyZoom(double x, double y, const QPoint &pivot)
|
||||
double minValue = minimumValue();
|
||||
double maxValue = maximumValue();
|
||||
|
||||
|
||||
QRectF canvas = canvasRect();
|
||||
|
||||
double xZoomedOut = canvas.width() / (maxTime - minTime);
|
||||
@@ -366,7 +386,6 @@ void GraphicsView::applyZoom(double x, double y, const QPoint &pivot)
|
||||
double scaleY = lerp(clamp(m_zoomY, 0.0, 1.0), -yZoomedOut, -yZoomedIn);
|
||||
|
||||
m_transform = QTransform::fromScale(scaleX, scaleY);
|
||||
|
||||
m_scene.setComponentTransform(m_transform);
|
||||
|
||||
QRectF sr = m_scene.sceneRect().adjusted(
|
||||
@@ -385,13 +404,32 @@ void GraphicsView::applyZoom(double x, double y, const QPoint &pivot)
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsView::insertKeyframe(double time)
|
||||
{
|
||||
const auto itemList = items();
|
||||
for (auto *item : itemList) {
|
||||
if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(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<CurveItem *>(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;
|
||||
|
||||
@@ -100,6 +100,8 @@ public:
|
||||
|
||||
void reset(const std::vector<CurveItem *> &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);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
****************************************************************************/
|
||||
|
||||
#include "handleitem.h"
|
||||
#include "keyframeitem.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QPainter>
|
||||
@@ -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<KeyframeItem *>(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.
|
||||
|
||||
@@ -49,6 +49,9 @@ public:
|
||||
|
||||
void setStyle(const CurveEditorStyle &style);
|
||||
|
||||
protected:
|
||||
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
|
||||
|
||||
private:
|
||||
HandleItemStyleOption m_style;
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
****************************************************************************/
|
||||
|
||||
#include "keyframeitem.h"
|
||||
#include "curveitem.h"
|
||||
#include "handleitem.h"
|
||||
|
||||
#include <QPainter>
|
||||
@@ -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<CurveItem *>(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<CurveItem *>(parentItem()))
|
||||
curveItem->setHandleVisibility(false);
|
||||
}
|
||||
|
||||
void KeyframeItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
SelectableItem::mouseReleaseEvent(event);
|
||||
if (auto *curveItem = qgraphicsitem_cast<CurveItem *>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
#include "selectableitem.h"
|
||||
#include "keyframeitem.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
namespace DesignTools {
|
||||
|
||||
SelectableItem::SelectableItem(QGraphicsItem *parent)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
****************************************************************************/
|
||||
|
||||
#include "treemodel.h"
|
||||
#include "treeitem.h"
|
||||
#include "detail/graphicsview.h"
|
||||
#include "treeitem.h"
|
||||
|
||||
#include <QIcon>
|
||||
|
||||
|
||||
@@ -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<CurveItem *> TreeView::selection()
|
||||
{
|
||||
std::vector<DesignTools::CurveItem *> items;
|
||||
for (auto &&index : selectionModel()->selectedRows(0)) {
|
||||
if (index.isValid()) {
|
||||
auto *treeItem = static_cast<TreeItem *>(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<CurveEditorModel *>(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<TreeItem *>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ public:
|
||||
|
||||
void setStyle(const CurveEditorStyle &style);
|
||||
|
||||
std::vector<CurveItem *> selection();
|
||||
|
||||
protected:
|
||||
QSize sizeHint() const override;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QColor;
|
||||
class QPalette;
|
||||
@@ -33,12 +35,10 @@ class QRectF;
|
||||
class QTransform;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<typename T>
|
||||
inline void freeClear(std::vector<T *> &vec)
|
||||
{
|
||||
for (auto *&el : vec)
|
||||
delete el;
|
||||
vec.clear();
|
||||
}
|
||||
|
||||
template<typename TV, typename TC>
|
||||
inline double clamp(const TV &val, const TC &lo, const TC &hi)
|
||||
{
|
||||
return val < lo ? lo : (val > hi ? hi : val);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline T lerp(double blend, const T &a, const T &b)
|
||||
{
|
||||
return (1.0 - blend) * a + blend * b;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline T reverseLerp(double blend, const T &a, const T &b)
|
||||
{
|
||||
return (blend - b) / (a - b);
|
||||
}
|
||||
|
||||
} // End namespace DesignTools.
|
||||
|
||||
@@ -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<int>(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.
|
||||
|
||||
@@ -26,18 +26,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <QPointF>
|
||||
#include <QVariant>
|
||||
|
||||
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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<TreeItem *> 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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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 <QVBoxLayout>
|
||||
|
||||
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
|
||||
@@ -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 <QDialog>
|
||||
|
||||
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
|
||||
@@ -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 <bindingproperty.h>
|
||||
#include <variantproperty.h>
|
||||
|
||||
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<DesignTools::TreeItem *> 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<DesignTools::Keyframe> 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<QEasingCurve>(ecurve));
|
||||
}
|
||||
|
||||
keyframes.push_back(keyframe);
|
||||
}
|
||||
}
|
||||
return DesignTools::AnimationCurve(keyframes);
|
||||
}
|
||||
|
||||
double AnimationCurveEditorModel::valueFromVariant(const QVariant &variant)
|
||||
{
|
||||
return variant.toDouble();
|
||||
}
|
||||
|
||||
void AnimationCurveEditorModel::reset(const std::vector<DesignTools::TreeItem *> &items)
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
initialize();
|
||||
|
||||
unsigned int counter = 0;
|
||||
for (auto *item : items) {
|
||||
item->setId(++counter);
|
||||
root()->addChild(item);
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
} // namespace QmlDesigner
|
||||
@@ -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 <qmltimelinekeyframegroup.h>
|
||||
|
||||
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<DesignTools::TreeItem *> &items);
|
||||
|
||||
double m_minTime;
|
||||
|
||||
double m_maxTime;
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -44,11 +44,13 @@
|
||||
#include <utils/algorithm.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QIntValidator>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QResizeEvent>
|
||||
#include <QSlider>
|
||||
#include <QIntValidator>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
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<const QIntValidator*>(m_currentFrame->validator());
|
||||
const_cast<QIntValidator*>(validator)->setRange(m_firstFrame->text().toInt(), m_lastFrame->text().toInt());
|
||||
auto validator = static_cast<const QIntValidator *>(m_currentFrame->validator());
|
||||
const_cast<QIntValidator *>(validator)->setRange(m_firstFrame->text().toInt(),
|
||||
m_lastFrame->text().toInt());
|
||||
}
|
||||
|
||||
void TimelineToolBar::resizeEvent(QResizeEvent *event)
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "animationcurvedialog.h"
|
||||
#include "animationcurveeditormodel.h"
|
||||
|
||||
#include <QToolBar>
|
||||
|
||||
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<QObject *> m_grp;
|
||||
|
||||
AnimationCurveDialog m_dialog;
|
||||
|
||||
AnimationCurveEditorModel *m_curveModel = nullptr;
|
||||
|
||||
QLabel *m_timelineLabel = nullptr;
|
||||
QLabel *m_stateLabel = nullptr;
|
||||
QSlider *m_scale = nullptr;
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include <nodemetainfo.h>
|
||||
#include <rewritertransaction.h>
|
||||
#include <variantproperty.h>
|
||||
#include <viewmanager.h>
|
||||
#include <qmldesignericons.h>
|
||||
#include <qmldesignerplugin.h>
|
||||
#include <qmlitemnode.h>
|
||||
@@ -50,7 +51,6 @@
|
||||
#include <qmlstate.h>
|
||||
#include <qmltimeline.h>
|
||||
#include <qmltimelinekeyframegroup.h>
|
||||
#include <viewmanager.h>
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
@@ -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<VariantProperty> &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<QmlTimeline> 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()
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "animationcurvedialog.h"
|
||||
#include "animationcurveeditormodel.h"
|
||||
#include "treeitem.h"
|
||||
|
||||
#include <abstractview.h>
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
@@ -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<int>(QMetaType::QEasingCurve))
|
||||
attachEasingCurve(pos.x(), data.value<QEasingCurve>(), 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())
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "animationcurveeditormodel.h"
|
||||
#include "timelineutils.h"
|
||||
#include <coreplugin/icontext.h>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qmldesignercorelib_global.h>
|
||||
#include "qmlmodelnodefacade.h"
|
||||
#include "qmlchangeset.h"
|
||||
#include "qmlmodelnodefacade.h"
|
||||
#include <qmldesignercorelib_global.h>
|
||||
|
||||
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<ModelNode> keyframes() const;
|
||||
|
||||
const QList<ModelNode> keyframePositions() const;
|
||||
|
||||
static bool isValidKeyframe(const ModelNode &node);
|
||||
@@ -83,4 +86,4 @@ public:
|
||||
QmlTimeline timeline() const;
|
||||
};
|
||||
|
||||
} //QmlDesigner
|
||||
} // namespace QmlDesigner
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
|
||||
#include "qmltimelinekeyframegroup.h"
|
||||
#include "abstractview.h"
|
||||
#include <nodelistproperty.h>
|
||||
#include <variantproperty.h>
|
||||
#include <metainfo.h>
|
||||
#include <invalidmodelnodeexception.h>
|
||||
#include "bindingproperty.h"
|
||||
#include "qmlitemnode.h"
|
||||
#include <invalidmodelnodeexception.h>
|
||||
#include <metainfo.h>
|
||||
#include <nodelistproperty.h>
|
||||
#include <variantproperty.h>
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
@@ -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<QPair<PropertyName, QVariant> > propertyPairList{{PropertyName("frame"), QVariant(currentFrame)},
|
||||
{PropertyName("value"), value}};
|
||||
const QList<QPair<PropertyName, QVariant>> 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<ModelNode> QmlTimelineKeyframeGroup::keyframes() const
|
||||
{
|
||||
return modelNode().defaultNodeListProperty().toModelNodeList();
|
||||
}
|
||||
|
||||
const QList<ModelNode> QmlTimelineKeyframeGroup::keyframePositions() const
|
||||
{
|
||||
QList<ModelNode> returnValues;
|
||||
@@ -257,14 +274,13 @@ const QList<ModelNode> 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
|
||||
|
||||
Reference in New Issue
Block a user