Connect CurveEditor edits to the timeline module

Change-Id: Ic00e0840da34bdbb8627b2fe2d8546a867b24966
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Knud Dollereder
2019-06-28 10:45:28 +02:00
parent a96095a2bf
commit 4a673896a6
43 changed files with 2145 additions and 531 deletions

View File

@@ -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()) {
for (auto e : extrema()) {
if (m_minY > e.y())
m_minY = e.y();
if (m_maxY < e.y())
m_maxY = e.y();
analyze();
}
for (auto &frame : qAsConst(m_frames)) {
if (frame.position().y() < m_minY)
m_minY = frame.position().y();
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 (frame.position().y() > m_maxY)
m_maxY = frame.position().y();
}
QVector<QPointF> points = easing.toCubicSpline();
int numSegments = points.count() / 3;
Keyframe current;
Keyframe tmp(start);
current.setInterpolation(Keyframe::Interpolation::Bezier);
tmp.setInterpolation(Keyframe::Interpolation::Bezier);
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)
if (segment.intersects(coord, radiusX, radiusY))
return true;
}
for (auto &x : segment.xForY(coord.y())) {
QLineF line(x, coord.y(), coord.x(), coord.y());
if (line.length() < radius)
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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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;

View File

@@ -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 \

View File

@@ -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

View 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.

View File

@@ -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;

View File

@@ -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;
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()));
return m_type;
}
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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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();

View File

@@ -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,10 +404,29 @@ 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,
QRectF gridRect = rect.adjusted(m_style.valueAxisWidth + m_style.canvasMargin,
m_style.timeAxisHeight + m_style.canvasMargin,
-m_style.canvasMargin,
-m_style.canvasMargin);

View File

@@ -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);

View File

@@ -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.

View File

@@ -49,6 +49,9 @@ public:
void setStyle(const CurveEditorStyle &style);
protected:
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
private:
HandleItemStyleOption m_style;
};

View File

@@ -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);
}
}
}

View File

@@ -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.

View File

@@ -26,8 +26,6 @@
#include "selectableitem.h"
#include "keyframeitem.h"
#include <QDebug>
namespace DesignTools {
SelectableItem::SelectableItem(QGraphicsItem *parent)

View File

@@ -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) {

View File

@@ -24,8 +24,8 @@
****************************************************************************/
#include "treemodel.h"
#include "treeitem.h"
#include "detail/graphicsview.h"
#include "treeitem.h"
#include <QIcon>

View File

@@ -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);
}
}
}

View File

@@ -49,6 +49,8 @@ public:
void setStyle(const CurveEditorStyle &style);
std::vector<CurveItem *> selection();
protected:
QSize sizeHint() const override;

View File

@@ -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();

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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";

View File

@@ -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

View File

@@ -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);
@@ -435,7 +476,8 @@ 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());
const_cast<QIntValidator *>(validator)->setRange(m_firstFrame->text().toInt(),
m_lastFrame->text().toInt());
}
void TimelineToolBar::resizeEvent(QResizeEvent *event)

View File

@@ -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;

View File

@@ -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);
}
}
}
@@ -382,7 +388,6 @@ void TimelineView::insertKeyframe(const ModelNode &target, const PropertyName &p
if (timeline.isValid() && targetNode.isValid()
&& QmlObjectNode::isValidQmlObjectNode(targetNode)) {
executeInTransaction("TimelineView::insertKeyframe", [=, &timeline, &targetNode]() {
targetNode.validId();
QmlTimelineKeyframeGroup timelineFrames(
@@ -395,7 +400,6 @@ void TimelineView::insertKeyframe(const ModelNode &target, const PropertyName &p
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);
}
}
@@ -485,8 +490,7 @@ void TimelineView::registerActions()
&& 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();
};

View File

@@ -25,6 +25,10 @@
#pragma once
#include "animationcurvedialog.h"
#include "animationcurveeditormodel.h"
#include "treeitem.h"
#include <abstractview.h>
#include <QPointer>

View File

@@ -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())

View File

@@ -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;

View File

@@ -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

View File

@@ -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
{
@@ -78,7 +77,6 @@ void QmlTimelineKeyframeGroup::setTarget(const ModelNode &target)
modelNode().bindingProperty("target").setExpression(target.id());
}
PropertyName QmlTimelineKeyframeGroup::propertyName() const
{
QTC_ASSERT(isValid(), return {});
@@ -166,10 +164,14 @@ void QmlTimelineKeyframeGroup::setValue(const QVariant &value, qreal currentFram
}
}
const QList<QPair<PropertyName, QVariant> > propertyPairList{{PropertyName("frame"), QVariant(currentFrame)},
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,8 +274,7 @@ const QList<ModelNode> QmlTimelineKeyframeGroup::keyframePositions() const
bool QmlTimelineKeyframeGroup::isValidKeyframe(const ModelNode &node)
{
return isValidQmlModelNodeFacade(node)
&& node.metaInfo().isValid()
return isValidQmlModelNodeFacade(node) && node.metaInfo().isValid()
&& node.metaInfo().isSubclassOf("QtQuick.Timeline.Keyframe");
}
@@ -297,4 +313,4 @@ void QmlTimelineKeyframeGroup::scaleAllKeyframes(qreal factor)
}
}
} // QmlDesigner
} // namespace QmlDesigner