forked from qt-creator/qt-creator
Implement playhead snapping
When Shift is down, playhead snaps to ruler ticks and keyframes. Task-number: QDS-1068 Change-Id: Ie793c3041c00ff01ec42f6045e1f3c44fc02da2c Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io> Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
@@ -159,4 +159,9 @@ void TimelineGraphicsLayout::activate()
|
||||
m_layout->activate();
|
||||
}
|
||||
|
||||
TimelineRulerSectionItem *TimelineGraphicsLayout::ruler() const
|
||||
{
|
||||
return m_rulerItem;
|
||||
}
|
||||
|
||||
} // End namespace QmlDesigner.
|
||||
|
@@ -74,6 +74,8 @@ public:
|
||||
|
||||
void activate();
|
||||
|
||||
TimelineRulerSectionItem *ruler() const;
|
||||
|
||||
private:
|
||||
QGraphicsLinearLayout *m_layout = nullptr;
|
||||
|
||||
|
@@ -171,12 +171,58 @@ void TimelineGraphicsScene::invalidateLayout()
|
||||
m_layout->invalidate();
|
||||
}
|
||||
|
||||
void TimelineGraphicsScene::updateKeyframePositionsCache()
|
||||
{
|
||||
auto kfPos = keyframePositions();
|
||||
std::sort(kfPos.begin(), kfPos.end());
|
||||
kfPos.erase(std::unique(kfPos.begin(), kfPos.end()), kfPos.end()); // remove duplicates
|
||||
|
||||
m_keyframePositionsCache = kfPos;
|
||||
}
|
||||
|
||||
// snap a frame to nearest keyframe or ruler tick
|
||||
qreal TimelineGraphicsScene::snap(qreal frame)
|
||||
{
|
||||
qreal frameTick = m_layout->ruler()->getFrameTick();
|
||||
qreal rulerTicksSnapframe = qRound(frame / frameTick) * frameTick;
|
||||
// get nearest keyframe to the input frame
|
||||
bool nearestKeyframeFound = false;
|
||||
qreal nearestKeyframe = 0;
|
||||
for (int i = 0; i < m_keyframePositionsCache.size(); ++i) {
|
||||
qreal kf_i = m_keyframePositionsCache[i];
|
||||
if (kf_i > frame) {
|
||||
nearestKeyframeFound = true;
|
||||
nearestKeyframe = kf_i;
|
||||
if (i > 0) {
|
||||
qreal kf_p = m_keyframePositionsCache[i - 1]; // previous kf
|
||||
if (frame - kf_p < kf_i - frame)
|
||||
nearestKeyframe = kf_p;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nearestKeyframeFound && !m_keyframePositionsCache.empty()) {
|
||||
// playhead past last keyframe case
|
||||
nearestKeyframe = m_keyframePositionsCache.last();
|
||||
nearestKeyframeFound = true;
|
||||
}
|
||||
|
||||
// return nearest snappable keyframe or ruler tick
|
||||
return nearestKeyframeFound && qAbs(nearestKeyframe - frame)
|
||||
< qAbs(rulerTicksSnapframe - frame) ? nearestKeyframe
|
||||
: rulerTicksSnapframe;
|
||||
}
|
||||
|
||||
void TimelineGraphicsScene::setCurrenFrame(const QmlTimeline &timeline, qreal frame)
|
||||
{
|
||||
if (timeline.isValid())
|
||||
if (timeline.isValid()) {
|
||||
if (QApplication::keyboardModifiers() & Qt::ShiftModifier) // playhead snapping
|
||||
frame = snap(frame);
|
||||
m_currentFrameIndicator->setPosition(frame);
|
||||
else
|
||||
} else {
|
||||
m_currentFrameIndicator->setPosition(0);
|
||||
}
|
||||
|
||||
invalidateCurrentValues();
|
||||
}
|
||||
|
@@ -88,6 +88,8 @@ public:
|
||||
qreal startFrame() const;
|
||||
qreal endFrame() const;
|
||||
|
||||
void updateKeyframePositionsCache();
|
||||
|
||||
qreal mapToScene(qreal x) const;
|
||||
qreal mapFromScene(qreal x) const;
|
||||
|
||||
@@ -165,6 +167,8 @@ private:
|
||||
QList<QGraphicsItem *> itemsAt(const QPointF &pos);
|
||||
|
||||
private:
|
||||
qreal snap(qreal frame);
|
||||
|
||||
TimelineWidget *m_parent = nullptr;
|
||||
|
||||
TimelineGraphicsLayout *m_layout = nullptr;
|
||||
@@ -175,6 +179,9 @@ private:
|
||||
|
||||
QList<TimelineKeyframeItem *> m_selectedKeyframes;
|
||||
|
||||
// sorted, unique cache of keyframes positions, used for snapping
|
||||
QVector<qreal> m_keyframePositionsCache;
|
||||
|
||||
int m_scrollOffset = 0;
|
||||
};
|
||||
|
||||
|
@@ -79,15 +79,15 @@ void TimelineFrameHandle::setHeight(int height)
|
||||
setRect(rect().x(), rect().y(), rect().width(), height);
|
||||
}
|
||||
|
||||
void TimelineFrameHandle::setPosition(qreal position)
|
||||
void TimelineFrameHandle::setPosition(qreal frame)
|
||||
{
|
||||
const qreal scenePos = mapFromFrameToScene(position);
|
||||
const qreal scenePos = mapFromFrameToScene(frame);
|
||||
QRectF newRect(scenePos - rect().width() / 2, rect().y(), rect().width(), rect().height());
|
||||
|
||||
if (!qFuzzyCompare(newRect.x(), rect().x())) {
|
||||
setRect(newRect);
|
||||
}
|
||||
m_position = position;
|
||||
m_position = frame;
|
||||
}
|
||||
|
||||
void TimelineFrameHandle::setPositionInteractive(const QPointF &position)
|
||||
|
@@ -50,7 +50,7 @@ public:
|
||||
explicit TimelineFrameHandle(TimelineItem *parent = nullptr);
|
||||
|
||||
void setHeight(int height);
|
||||
void setPosition(qreal position);
|
||||
void setPosition(qreal frame);
|
||||
void setPositionInteractive(const QPointF &postion) override;
|
||||
void commitPosition(const QPointF &point) override;
|
||||
qreal position() const;
|
||||
|
@@ -701,8 +701,6 @@ void TimelineRulerSectionItem::paint(QPainter *painter, const QStyleOptionGraphi
|
||||
|
||||
void TimelineRulerSectionItem::paintTicks(QPainter *painter)
|
||||
{
|
||||
const int totalWidth = size().width() / m_scaling + timelineScene()->scrollOffset() / m_scaling;
|
||||
|
||||
QFontMetrics fm(painter->font());
|
||||
|
||||
int minSpacingText = fm.horizontalAdvance(QString("X%1X").arg(rulerDuration()));
|
||||
@@ -734,8 +732,10 @@ void TimelineRulerSectionItem::paintTicks(QPainter *painter)
|
||||
}
|
||||
}
|
||||
|
||||
int height = size().height();
|
||||
m_frameTick = qreal(deltaLine);
|
||||
|
||||
int height = size().height();
|
||||
const int totalWidth = (size().width() + timelineScene()->scrollOffset()) / m_scaling;
|
||||
for (int i = timelineScene()->scrollOffset() / m_scaling; i < totalWidth; ++i) {
|
||||
if ((i % deltaText) == 0) {
|
||||
drawCenteredText(painter,
|
||||
@@ -759,6 +759,11 @@ void TimelineRulerSectionItem::paintTicks(QPainter *painter)
|
||||
}
|
||||
}
|
||||
|
||||
qreal TimelineRulerSectionItem::getFrameTick() const
|
||||
{
|
||||
return m_frameTick;
|
||||
}
|
||||
|
||||
void TimelineRulerSectionItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
TimelineItem::mousePressEvent(event);
|
||||
|
@@ -155,6 +155,7 @@ public:
|
||||
void setRulerScaleFactor(int scaling);
|
||||
|
||||
int getRulerScaleFactor() const;
|
||||
qreal getFrameTick() const;
|
||||
|
||||
qreal rulerScaling() const;
|
||||
qreal rulerDuration() const;
|
||||
@@ -187,6 +188,7 @@ private:
|
||||
qreal m_start = 0;
|
||||
qreal m_end = 0;
|
||||
qreal m_scaling = 1;
|
||||
qreal m_frameTick = 1.; // distance between 2 tick steps (in frames) on the ruler at current scale
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
||||
|
@@ -170,6 +170,7 @@ void TimelineView::instancePropertyChanged(const QList<QPair<ModelNode, Property
|
||||
{
|
||||
QmlTimeline timeline = currentTimeline();
|
||||
bool updated = false;
|
||||
bool keyframeChangeFlag = false;
|
||||
for (const auto &pair : propertyList) {
|
||||
if (pair.second == "startFrame" || pair.second == "endFrame") {
|
||||
if (QmlTimeline::isValidQmlTimeline(pair.first)) {
|
||||
@@ -183,6 +184,11 @@ void TimelineView::instancePropertyChanged(const QList<QPair<ModelNode, Property
|
||||
m_timelineWidget->graphicsScene()->invalidateCurrentValues();
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!keyframeChangeFlag && pair.second == "frame") {
|
||||
m_timelineWidget->graphicsScene()->updateKeyframePositionsCache();
|
||||
keyframeChangeFlag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user