Implement keyframes snapping

When shift is down while dragging keyframe(s), they snap to ruler ticks,
keyframes, and the playhead.

Task-number: QDS-1068
Change-Id: Iea5fec9e578d3f1db51c429cbd565ad145a90fe8
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Mahmoud Badri
2019-10-03 11:51:13 +03:00
parent 69719e7b3e
commit 2c2ffa8273
3 changed files with 31 additions and 15 deletions

View File

@@ -181,10 +181,10 @@ void TimelineGraphicsScene::updateKeyframePositionsCache()
} }
// snap a frame to nearest keyframe or ruler tick // snap a frame to nearest keyframe or ruler tick
qreal TimelineGraphicsScene::snap(qreal frame) qreal TimelineGraphicsScene::snap(qreal frame, bool snapToPlayhead)
{ {
qreal frameTick = m_layout->ruler()->getFrameTick(); qreal rulerFrameTick = m_layout->ruler()->getFrameTick();
qreal rulerTicksSnapframe = qRound(frame / frameTick) * frameTick; qreal nearestRulerTickFrame = qRound(frame / rulerFrameTick) * rulerFrameTick;
// get nearest keyframe to the input frame // get nearest keyframe to the input frame
bool nearestKeyframeFound = false; bool nearestKeyframeFound = false;
qreal nearestKeyframe = 0; qreal nearestKeyframe = 0;
@@ -202,23 +202,30 @@ qreal TimelineGraphicsScene::snap(qreal frame)
} }
} }
if (!nearestKeyframeFound && !m_keyframePositionsCache.empty()) { // playhead past last keyframe case
// playhead past last keyframe case if (!nearestKeyframeFound && !m_keyframePositionsCache.empty())
nearestKeyframe = m_keyframePositionsCache.last(); nearestKeyframe = m_keyframePositionsCache.last();
nearestKeyframeFound = true;
}
// return nearest snappable keyframe or ruler tick qreal playheadFrame = m_currentFrameIndicator->position();
return nearestKeyframeFound && qAbs(nearestKeyframe - frame)
< qAbs(rulerTicksSnapframe - frame) ? nearestKeyframe qreal dKeyframe = qAbs(nearestKeyframe - frame);
: rulerTicksSnapframe; qreal dPlayhead = snapToPlayhead ? qAbs(playheadFrame - frame) : 99999.;
qreal dRulerTick = qAbs(nearestRulerTickFrame - frame);
if (dKeyframe <= qMin(dPlayhead, dRulerTick))
return nearestKeyframe;
if (dRulerTick <= dPlayhead)
return nearestRulerTickFrame;
return playheadFrame;
} }
void TimelineGraphicsScene::setCurrenFrame(const QmlTimeline &timeline, qreal frame) void TimelineGraphicsScene::setCurrenFrame(const QmlTimeline &timeline, qreal frame)
{ {
if (timeline.isValid()) { if (timeline.isValid()) {
if (QApplication::keyboardModifiers() & Qt::ShiftModifier) // playhead snapping if (QApplication::keyboardModifiers() & Qt::ShiftModifier) // playhead snapping
frame = snap(frame); frame = snap(frame, false);
m_currentFrameIndicator->setPosition(frame); m_currentFrameIndicator->setPosition(frame);
} else { } else {
m_currentFrameIndicator->setPosition(0); m_currentFrameIndicator->setPosition(0);

View File

@@ -97,6 +97,8 @@ public:
QVector<qreal> keyframePositions() const; QVector<qreal> keyframePositions() const;
QVector<qreal> keyframePositions(const QmlTimelineKeyframeGroup &frames) const; QVector<qreal> keyframePositions(const QmlTimelineKeyframeGroup &frames) const;
qreal snap(qreal frame, bool snapToPlayhead = true);
void setRulerScaling(int scaling); void setRulerScaling(int scaling);
void commitCurrentFrame(qreal frame); void commitCurrentFrame(qreal frame);
@@ -167,7 +169,6 @@ private:
QList<QGraphicsItem *> itemsAt(const QPointF &pos); QList<QGraphicsItem *> itemsAt(const QPointF &pos);
private: private:
qreal snap(qreal frame);
TimelineWidget *m_parent = nullptr; TimelineWidget *m_parent = nullptr;

View File

@@ -90,7 +90,7 @@ void TimelineMoveTool::mouseMoveEvent(TimelineMovableAbstractItem *item,
return; return;
const qreal sourceFrame = qRound(current->mapFromSceneToFrame(current->rect().center().x())); const qreal sourceFrame = qRound(current->mapFromSceneToFrame(current->rect().center().x()));
const qreal targetFrame = qRound(current->mapFromSceneToFrame(event->scenePos().x())); qreal targetFrame = qRound(current->mapFromSceneToFrame(event->scenePos().x()));
qreal deltaFrame = targetFrame - sourceFrame - m_pressKeyframeDelta; qreal deltaFrame = targetFrame - sourceFrame - m_pressKeyframeDelta;
const qreal minFrame = scene()->startFrame(); const qreal minFrame = scene()->startFrame();
@@ -106,8 +106,16 @@ void TimelineMoveTool::mouseMoveEvent(TimelineMovableAbstractItem *item,
else if (firstFrame + deltaFrame < minFrame) else if (firstFrame + deltaFrame < minFrame)
deltaFrame = minFrame - firstFrame; deltaFrame = minFrame - firstFrame;
targetFrame = sourceFrame + deltaFrame;
if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // keyframe snapping
qreal snappedTargetFrame = scene()->snap(targetFrame);
deltaFrame += snappedTargetFrame - targetFrame;
targetFrame = snappedTargetFrame;
}
scene()->statusBarMessageChanged(tr(TimelineConstants::statusBarKeyframe) scene()->statusBarMessageChanged(tr(TimelineConstants::statusBarKeyframe)
.arg(sourceFrame + deltaFrame)); .arg(targetFrame));
const QList<TimelineKeyframeItem *> selectedKeyframes = scene()->selectedKeyframes(); const QList<TimelineKeyframeItem *> selectedKeyframes = scene()->selectedKeyframes();
for (auto *keyframe : selectedKeyframes) { for (auto *keyframe : selectedKeyframes) {