Add the ability to unify keyframe handles

Task-number: QDS-568
Change-Id: I5b102423e8e166d41edf199c42305cee102e8b54
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Knud Dollereder
2020-03-24 15:05:28 +01:00
parent 540ea616ec
commit 20e95f2f19
22 changed files with 207 additions and 30 deletions

View File

@@ -103,6 +103,15 @@ bool AnimationCurve::isFromData() const
return m_fromData; return m_fromData;
} }
bool AnimationCurve::hasUnified() const
{
for (auto &&frame : m_frames) {
if (frame.isUnified())
return true;
}
return false;
}
double AnimationCurve::minimumTime() const double AnimationCurve::minimumTime() const
{ {
if (!m_frames.empty()) if (!m_frames.empty())
@@ -144,6 +153,18 @@ std::string AnimationCurve::string() const
return sstream.str(); return sstream.str();
} }
QString AnimationCurve::unifyString() const
{
QString out;
for (auto &&frame : m_frames) {
if (frame.isUnified())
out.append("1");
else
out.append("0");
}
return out;
}
CurveSegment AnimationCurve::segment(double time) const CurveSegment AnimationCurve::segment(double time) const
{ {
CurveSegment seg; CurveSegment seg;

View File

@@ -50,6 +50,8 @@ public:
bool isFromData() const; bool isFromData() const;
bool hasUnified() const;
double minimumTime() const; double minimumTime() const;
double maximumTime() const; double maximumTime() const;
@@ -60,6 +62,8 @@ public:
std::string string() const; std::string string() const;
QString unifyString() const;
CurveSegment segment(double time) const; CurveSegment segment(double time) const;
std::vector<CurveSegment> segments() const; std::vector<CurveSegment> segments() const;

View File

@@ -86,6 +86,7 @@ QToolBar *CurveEditor::createToolBar(CurveEditorModel *model)
QAction *tangentStepAction = bar->addAction(QIcon(":/curveeditor/images/tangetToolsStepIcon.png"), "Step"); QAction *tangentStepAction = bar->addAction(QIcon(":/curveeditor/images/tangetToolsStepIcon.png"), "Step");
QAction *tangentSplineAction = bar->addAction(QIcon(":/curveeditor/images/tangetToolsSplineIcon.png"), "Spline"); QAction *tangentSplineAction = bar->addAction(QIcon(":/curveeditor/images/tangetToolsSplineIcon.png"), "Spline");
QAction *tangentDefaultAction = bar->addAction("Set Default"); QAction *tangentDefaultAction = bar->addAction("Set Default");
QAction *tangentUnifyAction = bar->addAction("Unify");
auto setLinearInterpolation = [this]() { auto setLinearInterpolation = [this]() {
m_view->setInterpolation(Keyframe::Interpolation::Linear); m_view->setInterpolation(Keyframe::Interpolation::Linear);
@@ -97,9 +98,12 @@ QToolBar *CurveEditor::createToolBar(CurveEditorModel *model)
m_view->setInterpolation(Keyframe::Interpolation::Bezier); m_view->setInterpolation(Keyframe::Interpolation::Bezier);
}; };
auto toggleUnifyKeyframe = [this]() { m_view->toggleUnified(); };
connect(tangentLinearAction, &QAction::triggered, setLinearInterpolation); connect(tangentLinearAction, &QAction::triggered, setLinearInterpolation);
connect(tangentStepAction, &QAction::triggered, setStepInterpolation); connect(tangentStepAction, &QAction::triggered, setStepInterpolation);
connect(tangentSplineAction, &QAction::triggered, setSplineInterpolation); connect(tangentSplineAction, &QAction::triggered, setSplineInterpolation);
connect(tangentUnifyAction, &QAction::triggered, toggleUnifyKeyframe);
Q_UNUSED(tangentLinearAction); Q_UNUSED(tangentLinearAction);
Q_UNUSED(tangentSplineAction); Q_UNUSED(tangentSplineAction);

View File

@@ -62,6 +62,9 @@ struct KeyframeItemStyleOption
double size = 10.0; double size = 10.0;
QColor color = QColor(200, 200, 0); QColor color = QColor(200, 200, 0);
QColor selectionColor = QColor(200, 200, 200); QColor selectionColor = QColor(200, 200, 200);
QColor lockedColor = QColor(50, 50, 50);
QColor unifiedColor = QColor(250, 50, 250);
QColor splitColor = QColor(0, 250, 0);
}; };
struct CurveItemStyleOption struct CurveItemStyleOption

View File

@@ -447,9 +447,11 @@ std::array<Keyframe, 3> CurveSegment::splitAt(double time)
out[0].setInterpolation(left().interpolation()); out[0].setInterpolation(left().interpolation());
out[0].setData(left().data()); out[0].setData(left().data());
out[0].setUnified(left().isUnified());
out[2].setInterpolation(right().interpolation()); out[2].setInterpolation(right().interpolation());
out[2].setData(right().data()); out[2].setData(right().data());
out[2].setUnified(right().isUnified());
return out; return out;
} }
} }

View File

@@ -297,6 +297,8 @@ void CurveItem::setCurve(const AnimationCurve &curve)
item->setComponentTransform(m_transform); item->setComponentTransform(m_transform);
m_keyframes.push_back(item); m_keyframes.push_back(item);
QObject::connect(item, &KeyframeItem::redrawCurve, this, &CurveItem::emitCurveChanged); QObject::connect(item, &KeyframeItem::redrawCurve, this, &CurveItem::emitCurveChanged);
QObject::connect(item, &KeyframeItem::keyframeMoved, this, &CurveItem::keyframeMoved);
QObject::connect(item, &KeyframeItem::handleMoved, this, &CurveItem::handleMoved);
} }
emitCurveChanged(); emitCurveChanged();
@@ -344,14 +346,24 @@ void CurveItem::setInterpolation(Keyframe::Interpolation interpolation)
emit curveChanged(id(), curve()); emit curveChanged(id(), curve());
} }
void CurveItem::toggleUnified()
{
if (m_keyframes.empty())
return;
for (auto *frame : m_keyframes) {
if (frame->selected())
frame->toggleUnified();
}
emit curveChanged(id(), curve());
}
void CurveItem::connect(GraphicsScene *scene) void CurveItem::connect(GraphicsScene *scene)
{ {
QObject::connect(this, &CurveItem::curveChanged, scene, &GraphicsScene::curveChanged); QObject::connect(this, &CurveItem::curveChanged, scene, &GraphicsScene::curveChanged);
for (auto *frame : m_keyframes) { QObject::connect(this, &CurveItem::keyframeMoved, scene, &GraphicsScene::keyframeMoved);
QObject::connect(frame, &KeyframeItem::keyframeMoved, scene, &GraphicsScene::keyframeMoved); QObject::connect(this, &CurveItem::handleMoved, scene, &GraphicsScene::handleMoved);
QObject::connect(frame, &KeyframeItem::handleMoved, scene, &GraphicsScene::handleMoved);
}
} }
void CurveItem::insertKeyframeByTime(double time) void CurveItem::insertKeyframeByTime(double time)

View File

@@ -27,6 +27,7 @@
#include "curveeditorstyle.h" #include "curveeditorstyle.h"
#include "curvesegment.h" #include "curvesegment.h"
#include "handleitem.h"
#include "keyframe.h" #include "keyframe.h"
#include "selectableitem.h" #include "selectableitem.h"
#include "treeitem.h" #include "treeitem.h"
@@ -47,6 +48,10 @@ class CurveItem : public CurveEditorItem
signals: signals:
void curveChanged(unsigned int id, const AnimationCurve &curve); void curveChanged(unsigned int id, const AnimationCurve &curve);
void keyframeMoved(KeyframeItem *item, const QPointF &direction);
void handleMoved(KeyframeItem *frame, HandleItem::Slot slot, double angle, double deltaLength);
public: public:
CurveItem(QGraphicsItem *parent = nullptr); CurveItem(QGraphicsItem *parent = nullptr);
@@ -100,6 +105,8 @@ public:
void setInterpolation(Keyframe::Interpolation interpolation); void setInterpolation(Keyframe::Interpolation interpolation);
void toggleUnified();
void connect(GraphicsScene *scene); void connect(GraphicsScene *scene);
void insertKeyframeByTime(double time); void insertKeyframeByTime(double time);

View File

@@ -37,6 +37,7 @@ GraphicsScene::GraphicsScene(QObject *parent)
: QGraphicsScene(parent) : QGraphicsScene(parent)
, m_dirty(true) , m_dirty(true)
, m_limits() , m_limits()
, m_doNotMoveItems(false)
{} {}
bool GraphicsScene::empty() const bool GraphicsScene::empty() const
@@ -64,6 +65,11 @@ double GraphicsScene::maximumValue() const
return limits().top(); return limits().top();
} }
void GraphicsScene::doNotMoveItems(bool val)
{
m_doNotMoveItems = val;
}
void GraphicsScene::addCurveItem(CurveItem *item) void GraphicsScene::addCurveItem(CurveItem *item)
{ {
m_dirty = true; m_dirty = true;
@@ -111,10 +117,9 @@ void GraphicsScene::handleUnderMouse(HandleItem *handle)
if (item == handle) if (item == handle)
continue; continue;
if (auto *handleItem = qgraphicsitem_cast<HandleItem *>(item)) { if (auto *keyItem = qgraphicsitem_cast<KeyframeItem *>(item)) {
if (handleItem->selected()) { if (keyItem->selected()) {
if (handleItem->slot() == handle->slot()) keyItem->setActivated(handle->isUnderMouse(), handle->slot());
handleItem->setActivated(handle->isUnderMouse());
} }
} }
} }
@@ -125,14 +130,29 @@ void GraphicsScene::handleMoved(KeyframeItem *frame,
double angle, double angle,
double deltaLength) double deltaLength)
{ {
if (m_doNotMoveItems)
return;
auto moveUnified = [handle, angle, deltaLength](KeyframeItem *key) {
if (key->isUnified()) {
if (handle == HandleItem::Slot::Left)
key->moveHandle(HandleItem::Slot::Right, angle, deltaLength);
else
key->moveHandle(HandleItem::Slot::Left, angle, deltaLength);
}
};
const auto itemList = items(); const auto itemList = items();
for (auto *item : itemList) { for (auto *item : itemList) {
if (item == frame)
continue;
if (auto *frameItem = qgraphicsitem_cast<KeyframeItem *>(item)) { if (auto *frameItem = qgraphicsitem_cast<KeyframeItem *>(item)) {
if (frameItem->selected()) if (item == frame) {
moveUnified(frameItem);
continue;
}
if (frameItem->selected()) {
frameItem->moveHandle(handle, angle, deltaLength); frameItem->moveHandle(handle, angle, deltaLength);
moveUnified(frameItem);
}
} }
} }
} }

View File

@@ -61,6 +61,8 @@ public:
double maximumValue() const; double maximumValue() const;
void doNotMoveItems(bool tmp);
void addCurveItem(CurveItem *item); void addCurveItem(CurveItem *item);
void setComponentTransform(const QTransform &transform); void setComponentTransform(const QTransform &transform);
@@ -90,6 +92,8 @@ private:
mutable bool m_dirty; mutable bool m_dirty;
mutable QRectF m_limits; mutable QRectF m_limits;
bool m_doNotMoveItems;
}; };
} // End namespace DesignTools. } // End namespace DesignTools.

View File

@@ -275,6 +275,18 @@ void GraphicsView::setInterpolation(Keyframe::Interpolation interpol)
viewport()->update(); viewport()->update();
} }
void GraphicsView::toggleUnified()
{
const auto itemList = items();
for (auto *item : itemList) {
if (auto *citem = qgraphicsitem_cast<CurveItem *>(item)) {
if (citem->hasSelection())
citem->toggleUnified();
}
}
viewport()->update();
}
void GraphicsView::resizeEvent(QResizeEvent *event) void GraphicsView::resizeEvent(QResizeEvent *event)
{ {
QGraphicsView::resizeEvent(event); QGraphicsView::resizeEvent(event);
@@ -433,6 +445,8 @@ QPointF GraphicsView::globalToRaster(const QPoint &point) const
void GraphicsView::applyZoom(double x, double y, const QPoint &pivot) void GraphicsView::applyZoom(double x, double y, const QPoint &pivot)
{ {
m_scene.doNotMoveItems(true);
QPointF pivotRaster(globalToRaster(pivot)); QPointF pivotRaster(globalToRaster(pivot));
m_zoomX = clamp(x, 0.0, 1.0); m_zoomX = clamp(x, 0.0, 1.0);
@@ -471,6 +485,8 @@ void GraphicsView::applyZoom(double x, double y, const QPoint &pivot)
QPointF deltaTransformed = pivotRaster - globalToRaster(pivot); QPointF deltaTransformed = pivotRaster - globalToRaster(pivot);
scrollContent(mapTimeToX(deltaTransformed.x()), mapValueToY(deltaTransformed.y())); scrollContent(mapTimeToX(deltaTransformed.x()), mapValueToY(deltaTransformed.y()));
} }
m_scene.doNotMoveItems(false);
} }
void GraphicsView::insertKeyframe(double time, bool allVisibleCurves) void GraphicsView::insertKeyframe(double time, bool allVisibleCurves)

View File

@@ -112,6 +112,8 @@ public:
void setInterpolation(Keyframe::Interpolation interpol); void setInterpolation(Keyframe::Interpolation interpol);
void toggleUnified();
protected: protected:
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;

View File

@@ -130,7 +130,7 @@ void HandleItem::setStyle(const CurveEditorStyle &style)
QVariant HandleItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) QVariant HandleItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{ {
if (change == ItemPositionChange) { if (change == ItemPositionChange) {
if (KeyframeItem *parent = qgraphicsitem_cast<KeyframeItem *>(parentItem())) { if (qgraphicsitem_cast<KeyframeItem *>(parentItem())) {
QPointF pos = value.toPointF(); QPointF pos = value.toPointF();
if (m_slot == HandleItem::Slot::Left) { if (m_slot == HandleItem::Slot::Left) {
if (pos.x() > 0.0) if (pos.x() > 0.0)

View File

@@ -64,14 +64,22 @@ void KeyframeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
Q_UNUSED(option) Q_UNUSED(option)
Q_UNUSED(widget) Q_UNUSED(widget)
QColor mainColor = selected() ? m_style.selectionColor : m_style.color;
QColor borderColor = isUnified() ? m_style.unifiedColor : m_style.splitColor;
if (locked()) {
mainColor = m_style.lockedColor;
borderColor = m_style.lockedColor;
}
QPen pen = painter->pen(); QPen pen = painter->pen();
pen.setColor(Qt::black); pen.setWidthF(1.);
pen.setColor(borderColor);
painter->save(); painter->save();
painter->setPen(pen); painter->setPen(pen);
painter->setBrush(locked() ? Qt::black : (selected() ? Qt::red : m_style.color)); painter->setBrush(mainColor);
painter->drawEllipse(boundingRect()); painter->drawEllipse(boundingRect());
painter->restore(); painter->restore();
} }
@@ -93,6 +101,11 @@ Keyframe KeyframeItem::keyframe() const
return m_frame; return m_frame;
} }
bool KeyframeItem::isUnified() const
{
return m_frame.isUnified();
}
bool KeyframeItem::hasLeftHandle() const bool KeyframeItem::hasLeftHandle() const
{ {
return m_frame.hasLeftHandle(); return m_frame.hasLeftHandle();
@@ -108,11 +121,6 @@ QTransform KeyframeItem::transform() const
return m_transform; return m_transform;
} }
bool KeyframeItem::contains(HandleItem *handle, const QPointF &point) const
{
return false;
}
void KeyframeItem::setHandleVisibility(bool visible) void KeyframeItem::setHandleVisibility(bool visible)
{ {
m_visibleOverride = visible; m_visibleOverride = visible;
@@ -195,6 +203,31 @@ void KeyframeItem::setKeyframe(const Keyframe &keyframe)
setPos(m_transform.map(m_frame.position())); setPos(m_transform.map(m_frame.position()));
} }
void KeyframeItem::toggleUnified()
{
if (!m_left || !m_right)
return;
if (m_frame.isUnified())
m_frame.setUnified(false);
else
m_frame.setUnified(true);
}
void KeyframeItem::setActivated(bool active, HandleItem::Slot slot)
{
if (isUnified() && m_left && m_right) {
m_left->setActivated(active);
m_right->setActivated(active);
return;
}
if (slot == HandleItem::Slot::Left && m_left)
m_left->setActivated(active);
else if (slot == HandleItem::Slot::Right && m_right)
m_right->setActivated(active);
}
void KeyframeItem::setInterpolation(Keyframe::Interpolation interpolation) void KeyframeItem::setInterpolation(Keyframe::Interpolation interpolation)
{ {
m_frame.setInterpolation(interpolation); m_frame.setInterpolation(interpolation);

View File

@@ -66,14 +66,14 @@ public:
Keyframe keyframe() const; Keyframe keyframe() const;
bool isUnified() const;
bool hasLeftHandle() const; bool hasLeftHandle() const;
bool hasRightHandle() const; bool hasRightHandle() const;
QTransform transform() const; QTransform transform() const;
bool contains(HandleItem *handle, const QPointF &point) const;
void setHandleVisibility(bool visible); void setHandleVisibility(bool visible);
void setComponentTransform(const QTransform &transform); void setComponentTransform(const QTransform &transform);
@@ -82,6 +82,10 @@ public:
void setKeyframe(const Keyframe &keyframe); void setKeyframe(const Keyframe &keyframe);
void toggleUnified();
void setActivated(bool active, HandleItem::Slot slot);
void setInterpolation(Keyframe::Interpolation interpolation); void setInterpolation(Keyframe::Interpolation interpolation);
void setLeftHandle(const QPointF &pos); void setLeftHandle(const QPointF &pos);

View File

@@ -31,6 +31,7 @@ namespace DesignTools {
Keyframe::Keyframe() Keyframe::Keyframe()
: m_interpolation(Interpolation::Undefined) : m_interpolation(Interpolation::Undefined)
, m_unified(false)
, m_position() , m_position()
, m_leftHandle() , m_leftHandle()
, m_rightHandle() , m_rightHandle()
@@ -39,6 +40,7 @@ Keyframe::Keyframe()
Keyframe::Keyframe(const QPointF &position) Keyframe::Keyframe(const QPointF &position)
: m_interpolation(Interpolation::Linear) : m_interpolation(Interpolation::Linear)
, m_unified(false)
, m_position(position) , m_position(position)
, m_leftHandle() , m_leftHandle()
, m_rightHandle() , m_rightHandle()
@@ -47,6 +49,7 @@ Keyframe::Keyframe(const QPointF &position)
Keyframe::Keyframe(const QPointF &position, const QVariant &data) Keyframe::Keyframe(const QPointF &position, const QVariant &data)
: m_interpolation(Interpolation::Undefined) : m_interpolation(Interpolation::Undefined)
, m_unified(false)
, m_position(position) , m_position(position)
, m_leftHandle() , m_leftHandle()
, m_rightHandle() , m_rightHandle()
@@ -57,6 +60,7 @@ Keyframe::Keyframe(const QPointF &position, const QVariant &data)
Keyframe::Keyframe(const QPointF &position, const QPointF &leftHandle, const QPointF &rightHandle) Keyframe::Keyframe(const QPointF &position, const QPointF &leftHandle, const QPointF &rightHandle)
: m_interpolation(Interpolation::Bezier) : m_interpolation(Interpolation::Bezier)
, m_unified(false)
, m_position(position) , m_position(position)
, m_leftHandle(leftHandle) , m_leftHandle(leftHandle)
, m_rightHandle(rightHandle) , m_rightHandle(rightHandle)
@@ -73,6 +77,11 @@ bool Keyframe::hasData() const
return m_data.isValid(); return m_data.isValid();
} }
bool Keyframe::isUnified() const
{
return m_unified;
}
bool Keyframe::hasLeftHandle() const bool Keyframe::hasLeftHandle() const
{ {
return !m_leftHandle.isNull(); return !m_leftHandle.isNull();
@@ -143,6 +152,11 @@ void Keyframe::setPosition(const QPointF &pos)
m_position = pos; m_position = pos;
} }
void Keyframe::setUnified(bool unified)
{
m_unified = unified;
}
void Keyframe::setLeftHandle(const QPointF &pos) void Keyframe::setLeftHandle(const QPointF &pos)
{ {
m_leftHandle = pos; m_leftHandle = pos;

View File

@@ -47,6 +47,8 @@ public:
bool hasData() const; bool hasData() const;
bool isUnified() const;
bool hasLeftHandle() const; bool hasLeftHandle() const;
bool hasRightHandle() const; bool hasRightHandle() const;
@@ -63,6 +65,8 @@ public:
Interpolation interpolation() const; Interpolation interpolation() const;
void setUnified(bool unified);
void setPosition(const QPointF &pos); void setPosition(const QPointF &pos);
void setLeftHandle(const QPointF &pos); void setLeftHandle(const QPointF &pos);
@@ -76,6 +80,8 @@ public:
private: private:
Interpolation m_interpolation = Interpolation::Undefined; Interpolation m_interpolation = Interpolation::Undefined;
bool m_unified;
QPointF m_position; QPointF m_position;
QPointF m_leftHandle; QPointF m_leftHandle;

View File

@@ -311,6 +311,16 @@ AnimationCurve PropertyTreeItem::curve() const
return m_curve; return m_curve;
} }
bool PropertyTreeItem::hasUnified() const
{
return m_curve.hasUnified();
}
QString PropertyTreeItem::unifyString() const
{
return m_curve.unifyString();
}
void PropertyTreeItem::setCurve(const AnimationCurve &curve) void PropertyTreeItem::setCurve(const AnimationCurve &curve)
{ {
m_curve = curve; m_curve = curve;

View File

@@ -155,6 +155,10 @@ public:
AnimationCurve curve() const; AnimationCurve curve() const;
bool hasUnified() const;
QString unifyString() const;
void setCurve(const AnimationCurve &curve); void setCurve(const AnimationCurve &curve);
void setComponent(const Component &comp); void setComponent(const Component &comp);

View File

@@ -230,6 +230,19 @@ DesignTools::AnimationCurve AnimationCurveEditorModel::createDoubleCurve(
{ {
std::vector<DesignTools::Keyframe> keyframes = createKeyframes(group.keyframePositions()); std::vector<DesignTools::Keyframe> keyframes = createKeyframes(group.keyframePositions());
keyframes = resolveSmallCurves(keyframes); keyframes = resolveSmallCurves(keyframes);
QString str;
ModelNode target = group.modelNode();
if (target.hasAuxiliaryData("unified"))
str = target.auxiliaryData("unified").toString();
if (str.size() == static_cast<int>(keyframes.size())) {
for (int i = 0; i < str.size(); ++i) {
if (str.at(i) == '1')
keyframes[i].setUnified(true);
}
}
return DesignTools::AnimationCurve(keyframes); return DesignTools::AnimationCurve(keyframes);
} }

View File

@@ -253,11 +253,6 @@ void TimelineToolBar::openAnimationCurveEditor()
m_dialog->show(); m_dialog->show();
} }
void TimelineToolBar::updateCurve(DesignTools::PropertyTreeItem *item)
{
DesignTools::AnimationCurve curve = item->curve();
}
void TimelineToolBar::createLeftControls() void TimelineToolBar::createLeftControls()
{ {
auto addActionToGroup = [&](QAction *action) { auto addActionToGroup = [&](QAction *action) {

View File

@@ -91,8 +91,6 @@ public:
void openAnimationCurveEditor(); void openAnimationCurveEditor();
void updateCurve(DesignTools::PropertyTreeItem *item);
protected: protected:
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;

View File

@@ -340,6 +340,11 @@ void TimelineWidget::updateAnimationCurve(DesignTools::PropertyTreeItem *item)
groupNode.setAuxiliaryData("pinned", true); groupNode.setAuxiliaryData("pinned", true);
else else
groupNode.removeAuxiliaryData("pinned"); groupNode.removeAuxiliaryData("pinned");
if (item->hasUnified())
groupNode.setAuxiliaryData("unified", item->unifyString());
else
groupNode.removeAuxiliaryData("unified");
} }
auto replaceKeyframes = [&group, item, this]() { auto replaceKeyframes = [&group, item, this]() {