diff options
author | Knud Dollereder <knud.dollereder@qt.io> | 2019-06-03 15:12:47 +0200 |
---|---|---|
committer | Knud Dollereder <knud.dollereder@qt.io> | 2019-06-04 09:00:04 +0000 |
commit | bb54345474c928250f65f778fcfc2fb061f72406 (patch) | |
tree | f866babde39b2cb1fd4a65bea6c02845989457f2 /src | |
parent | 69e0ce6b1aee59585f141a320cef4f980e6ae7dc (diff) |
Implement insertion and deletion of keyframes
Change-Id: I3d0c10a765588a53ae2053b74b8a3173c440e982
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/curveeditor/animationcurve.cpp | 58 | ||||
-rw-r--r-- | src/curveeditor/animationcurve.h | 8 | ||||
-rw-r--r-- | src/curveeditor/curveeditorstyle.h | 3 | ||||
-rw-r--r-- | src/curveeditor/detail/curveitem.cpp | 70 | ||||
-rw-r--r-- | src/curveeditor/detail/curveitem.h | 10 | ||||
-rw-r--r-- | src/curveeditor/detail/curvesegment.cpp | 47 | ||||
-rw-r--r-- | src/curveeditor/detail/curvesegment.h | 10 | ||||
-rw-r--r-- | src/curveeditor/detail/graphicsview.cpp | 27 | ||||
-rw-r--r-- | src/curveeditor/detail/graphicsview.h | 4 | ||||
-rw-r--r-- | src/curveeditor/detail/selector.cpp | 7 | ||||
-rw-r--r-- | src/curveeditor/detail/utils.cpp | 10 | ||||
-rw-r--r-- | src/curveeditor/detail/utils.h | 16 |
12 files changed, 236 insertions, 34 deletions
diff --git a/src/curveeditor/animationcurve.cpp b/src/curveeditor/animationcurve.cpp index 8e2d522..17e17dd 100644 --- a/src/curveeditor/animationcurve.cpp +++ b/src/curveeditor/animationcurve.cpp @@ -25,6 +25,7 @@ #include "animationcurve.h" #include "detail/curvesegment.h" +#include "detail/utils.h" #include <QLineF> @@ -91,6 +92,19 @@ 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<Keyframe> AnimationCurve::keyframes() const { return m_frames; @@ -141,7 +155,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 radius) const { if (m_frames.size() < 2) return false; @@ -152,13 +166,13 @@ 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() - radius) + || current.containsX(coord.x()) + || current.containsX(coord.x() + radius)) { influencer.push_back(current); } @@ -184,4 +198,38 @@ bool AnimationCurve::intersects(const QPointF &coord, double radius) return false; } +void AnimationCurve::insert(double time) +{ + CurveSegment seg = segment(time); + + if (!seg.isValid()) + return; + + for (double t : seg.tForX(time)) { + auto p0 = lerp(t, seg.left().position(), seg.left().rightHandle()); + auto p1 = lerp(t, seg.left().rightHandle(), seg.right().leftHandle()); + auto p2 = lerp(t, seg.right().leftHandle(), seg.right().position()); + + auto p01 = lerp(t, p0, p1); + auto p12 = lerp(t, p1, p2); + auto p01p12 = lerp(t, p01, p12); + + std::vector<Keyframe> frames = { + Keyframe(seg.left().position(), seg.left().leftHandle(), p0), + Keyframe(p01p12, p01, p12), + Keyframe(seg.right().position(), p2, seg.right().rightHandle()) + }; + + 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()); + } + } +} + } // End namespace DesignTools. diff --git a/src/curveeditor/animationcurve.h b/src/curveeditor/animationcurve.h index 0533e47..7e483cf 100644 --- a/src/curveeditor/animationcurve.h +++ b/src/curveeditor/animationcurve.h @@ -31,6 +31,8 @@ namespace DesignTools { +class CurveSegment; + class AnimationCurve { public: @@ -48,6 +50,8 @@ public: double maximumValue() const; + CurveSegment segment(double time) const; + std::vector<Keyframe> keyframes() const; std::vector<QPointF> extrema() const; @@ -56,7 +60,9 @@ public: std::vector<double> xForY(double y, uint segment) const; - bool intersects(const QPointF &coord, double radius); + bool intersects(const QPointF &coord, double radius) const; + + void insert(double time); private: std::vector<Keyframe> m_frames; diff --git a/src/curveeditor/curveeditorstyle.h b/src/curveeditor/curveeditorstyle.h index 03ea11c..835d138 100644 --- a/src/curveeditor/curveeditorstyle.h +++ b/src/curveeditor/curveeditorstyle.h @@ -84,6 +84,9 @@ struct Shortcuts Shortcut zoom = Shortcut(Qt::RightButton, Qt::AltModifier); Shortcut pan = Shortcut(Qt::MiddleButton, Qt::AltModifier); Shortcut frameAll = Shortcut(Qt::NoModifier, Qt::Key_A); + + Shortcut insertKeyframe = Shortcut(Qt::MiddleButton, Qt::NoModifier); + Shortcut deleteKeyframe = Shortcut(Qt::NoModifier, Qt::Key_Delete); }; struct CurveEditorStyle diff --git a/src/curveeditor/detail/curveitem.cpp b/src/curveeditor/detail/curveitem.cpp index 5973e70..43d3edc 100644 --- a/src/curveeditor/detail/curveitem.cpp +++ b/src/curveeditor/detail/curveitem.cpp @@ -31,8 +31,18 @@ #include <QPainter> #include <QPainterPath> +#include <algorithm> + namespace DesignTools { +template<typename T> +void freeClear(std::vector<T *> &vec) +{ + for (auto *&el : vec) + delete el; + vec.clear(); +} + CurveItem::CurveItem(QGraphicsItem *parent) : QGraphicsObject(parent) , m_id(0) @@ -58,17 +68,7 @@ CurveItem::CurveItem(unsigned int id, const AnimationCurve &curve, QGraphicsItem 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); - m_keyframes.push_back(item); - } + setCurve(curve); } CurveItem::~CurveItem() {} @@ -128,6 +128,11 @@ bool CurveItem::isDirty() const return m_itemDirty; } +bool CurveItem::isUnderMouse() const +{ + return m_underMouse; +} + bool CurveItem::hasSelection() const { for (auto *frame : m_keyframes) { @@ -185,6 +190,20 @@ void CurveItem::setDirty(bool dirty) m_itemDirty = dirty; } +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; @@ -221,4 +240,33 @@ 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; + m_pathDirty = true; + update(); +} + } // End namespace DesignTools. diff --git a/src/curveeditor/detail/curveitem.h b/src/curveeditor/detail/curveitem.h index 90e68e2..5272996 100644 --- a/src/curveeditor/detail/curveitem.h +++ b/src/curveeditor/detail/curveitem.h @@ -59,6 +59,8 @@ public: bool isDirty() const; + bool isUnderMouse() const; + bool hasSelection() const; unsigned int id() const; @@ -67,6 +69,8 @@ public: void setDirty(bool dirty); + void setCurve(const AnimationCurve &curve); + QRectF setComponentTransform(const QTransform &transform); void setStyle(const CurveEditorStyle &style); @@ -75,9 +79,15 @@ public: void setIsUnderMouse(bool under); + void insertKeyframeByTime(double time); + + void deleteSelectedKeyframes(); + private: QPainterPath path() const; + void emitCurveChanged(); + unsigned int m_id; CurveItemStyleOption m_style; diff --git a/src/curveeditor/detail/curvesegment.cpp b/src/curveeditor/detail/curvesegment.cpp index 40f675f..67bc8e0 100644 --- a/src/curveeditor/detail/curvesegment.cpp +++ b/src/curveeditor/detail/curvesegment.cpp @@ -145,6 +145,21 @@ CurveSegment::CurveSegment(const Keyframe &left, const Keyframe &right) , m_right(right) {} +bool CurveSegment::isValid() +{ + return m_left.position() != m_right.position(); +} + +Keyframe CurveSegment::left() const +{ + return m_left; +} + +Keyframe CurveSegment::right() const +{ + return m_right; +} + bool CurveSegment::containsX(double x) const { return m_left.position().x() <= x && m_right.position().x() >= x; @@ -210,6 +225,38 @@ std::vector<QPointF> CurveSegment::extrema() const return out; } +std::vector<double> CurveSegment::tForX(double x) +{ + 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; +} + +std::vector<double> CurveSegment::tForY(double y) +{ + 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; diff --git a/src/curveeditor/detail/curvesegment.h b/src/curveeditor/detail/curvesegment.h index ce2d700..9c7ceb6 100644 --- a/src/curveeditor/detail/curvesegment.h +++ b/src/curveeditor/detail/curvesegment.h @@ -40,12 +40,22 @@ public: CurveSegment(const Keyframe &first, const Keyframe &last); + bool isValid(); + + Keyframe left() const; + + Keyframe right() const; + bool containsX(double x) const; QPointF evaluate(double t) const; std::vector<QPointF> extrema() const; + std::vector<double> tForX(double x); + + std::vector<double> tForY(double y); + std::vector<double> yForX(double x) const; std::vector<double> xForY(double y) const; diff --git a/src/curveeditor/detail/graphicsview.cpp b/src/curveeditor/detail/graphicsview.cpp index ae60888..4162f53 100644 --- a/src/curveeditor/detail/graphicsview.cpp +++ b/src/curveeditor/detail/graphicsview.cpp @@ -220,6 +220,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 +230,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)) { @@ -385,6 +392,26 @@ 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( diff --git a/src/curveeditor/detail/graphicsview.h b/src/curveeditor/detail/graphicsview.h index 1975e36..7888b7b 100644 --- a/src/curveeditor/detail/graphicsview.h +++ b/src/curveeditor/detail/graphicsview.h @@ -122,6 +122,10 @@ protected: private: void applyZoom(double x, double y, const QPoint &pivot = QPoint()); + void insertKeyframe(double time); + + void deleteSelectedKeyframes(); + void drawGrid(QPainter *painter, const QRectF &rect); void drawExtremaX(QPainter *painter, const QRectF &rect); diff --git a/src/curveeditor/detail/selector.cpp b/src/curveeditor/detail/selector.cpp index b6e7a8f..5e9ffb5 100644 --- a/src/curveeditor/detail/selector.cpp +++ b/src/curveeditor/detail/selector.cpp @@ -75,11 +75,12 @@ 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; - if (m_shortcut == m_shortcuts.newSelection || m_shortcut == m_shortcuts.addToSelection + 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) { if (view->hasActiveItem()) diff --git a/src/curveeditor/detail/utils.cpp b/src/curveeditor/detail/utils.cpp index 4933bcb..4bc1cd2 100644 --- a/src/curveeditor/detail/utils.cpp +++ b/src/curveeditor/detail/utils.cpp @@ -31,16 +31,6 @@ namespace DesignTools { -double clamp(double val, double lo, double hi) -{ - return val < lo ? lo : (val > hi ? hi : val); -} - -double lerp(double blend, double a, double b) -{ - return (1.0 - blend) * a + blend * b; -} - double scaleX(const QTransform &transform) { return transform.m11(); diff --git a/src/curveeditor/detail/utils.h b/src/curveeditor/detail/utils.h index 812c5d0..d53086a 100644 --- a/src/curveeditor/detail/utils.h +++ b/src/curveeditor/detail/utils.h @@ -33,10 +33,6 @@ class QTransform; 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); @@ -47,4 +43,16 @@ QRectF bbox(const QRectF &rect, const QTransform &transform); QPalette singleColorPalette(const QColor &color); +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; +} + } // End namespace DesignTools. |