diff options
author | Tony Leinonen <tony.leinonen@qt.io> | 2021-08-10 16:19:43 +0300 |
---|---|---|
committer | Tony Leinonen <tony.leinonen@qt.io> | 2021-09-09 05:09:49 +0000 |
commit | f7489252dff158090ea0873ecde3335a40680ae9 (patch) | |
tree | 41a14afa846275f96298b0b961a87773b54d11f9 | |
parent | b485890b1999950fbe0b53d2cb784b44636f7ac5 (diff) | |
download | qt-creator-f7489252dff158090ea0873ecde3335a40680ae9.tar.gz |
QmlDesigner: Add animation playback for timeline
Adds animation playback, playback speed and looping inputs in the toolbar
-Animation can be seen in the form editor in real time
-Play button toggles the animation playback
-Playback speed can be manually set
-Looping can be toggled on and off
-Looping range can be manually set
Looping range can be created and edited in multiple ways. These shortcuts
are currently placeholders and should be changed if needed.
-LeftMouseButton+CTRL will set the loop start point and dragging
the mouse will let you paint the range.
-Hovering over the looping start or end point then dragging with
LeftMouseButton+CTRL down will allow individual point movement
-Pressing SHIFT will enable toolbar snapping when painting/moving
looping points.
will allow individual movement by dragging it with LeftMouseButton
-Clicking a component in the timeline with LeftMouseButton+CTRL
will set the components start and end time as the loop range.
Using LeftMouseButton+CTRL+SHIFT instead will extend the
current loop range to include the clicked component.
Keyframes work the same way.
-Using the selection tool by dragging the timeline with
LeftMouseButton+CTRL will set the loop range to contain all the
selected components. Using LeftMouseButton+CTRL+SHIFT instead
will extend the current loop range to include the animations
start, mid or end time, depending on which overlap with the selection
tool rectangle. Keyframes work the same way.
Task-number: QDS-1335
Change-Id: I8a623bae6ab43b41f894f2a33a1a597a48f82c68
Reviewed-by: Knud Dollereder <knud.dollereder@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
16 files changed, 378 insertions, 30 deletions
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h b/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h index 622dc366ed..54119db9fd 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h @@ -64,6 +64,7 @@ const char C_TO_START[] = "QmlDesigner.ToStart"; const char C_TO_END[] = "QmlDesigner.ToEnd"; const char C_PREVIOUS[] = "QmlDesigner.Previous"; const char C_PLAY[] = "QmlDesigner.Play"; +const char C_LOOP_PLAYBACK[] = "QmlDesigner.LoopPlayback"; const char C_NEXT[] = "QmlDesigner.Next"; const char C_AUTO_KEYFRAME[] = "QmlDesigner.AutoKeyframe"; const char C_CURVE_PICKER[] = "QmlDesigner.CurvePicker"; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp index 0b67542fc6..704d7c2b0d 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp @@ -598,6 +598,9 @@ void TimelineGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event) void TimelineGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos())); + + m_layout->ruler()->updatePlaybackLoop(event); + m_tools.mouseMoveEvent(topItem, event); QGraphicsScene::mouseMoveEvent(event); } @@ -605,6 +608,10 @@ void TimelineGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void TimelineGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos())); + + + m_layout->ruler()->updatePlaybackLoop(event); + /* The tool has handle the event last. */ QGraphicsScene::mouseReleaseEvent(event); m_tools.mouseReleaseEvent(topItem, event); @@ -695,6 +702,11 @@ void TimelineGraphicsScene::invalidateSections() invalidateLayout(); } +TimelineRulerSectionItem *TimelineGraphicsScene::layoutRuler() const +{ + return m_layout->ruler(); +} + TimelineView *TimelineGraphicsScene::timelineView() const { return m_parent->timelineView(); diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h index ade4edfeb8..b183639326 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h @@ -130,6 +130,7 @@ public: void setStartFrame(int frame); void setEndFrame(int frame); + TimelineRulerSectionItem *layoutRuler() const; TimelineView *timelineView() const; TimelineWidget *timelineWidget() const; TimelineToolBar *toolBar() const; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp index 1b0b2cb7e5..a5121b39bb 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp @@ -131,6 +131,16 @@ TimelineKeyframeItem *TimelineMovableAbstractItem::asTimelineKeyframeItem(QGraph return nullptr; } +TimelineBarItem *TimelineMovableAbstractItem::asTimelineBarItem(QGraphicsItem *item) +{ + auto movableItem = TimelineMovableAbstractItem::cast(item); + + if (movableItem) + return movableItem->asTimelineBarItem(); + + return nullptr; +} + qreal TimelineMovableAbstractItem::rulerScaling() const { return qobject_cast<AbstractScrollGraphicsScene *>(scene())->rulerScaling(); @@ -156,6 +166,12 @@ TimelineFrameHandle *TimelineMovableAbstractItem::asTimelineFrameHandle() return nullptr; } + +TimelineBarItem *TimelineMovableAbstractItem::asTimelineBarItem() +{ + return nullptr; +} + bool TimelineMovableAbstractItem::isLocked() const { return false; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h index d79101b4d9..b38406cb78 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h @@ -35,6 +35,7 @@ namespace QmlDesigner { class AbstractScrollGraphicsScene; class TimelineKeyframeItem; class TimelineFrameHandle; +class TimelineBarItem; class TimelineMovableAbstractItem : public QGraphicsRectItem { @@ -51,6 +52,7 @@ public: static TimelineMovableAbstractItem *topMoveableItem(const QList<QGraphicsItem *> &items); static void emitScrollOffsetChanged(QGraphicsItem *item); static TimelineKeyframeItem *asTimelineKeyframeItem(QGraphicsItem *item); + static TimelineBarItem *asTimelineBarItem(QGraphicsItem *item); qreal rulerScaling() const; @@ -68,6 +70,7 @@ public: virtual TimelineKeyframeItem *asTimelineKeyframeItem(); virtual TimelineFrameHandle *asTimelineFrameHandle(); + virtual TimelineBarItem *asTimelineBarItem(); virtual bool isLocked() const; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp index 0508606c11..82c84abeb6 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp @@ -73,10 +73,25 @@ void TimelineMoveTool::mousePressEvent(TimelineMovableAbstractItem *item, if (currentItem() && currentItem()->isLocked()) return; + TimelineGraphicsScene *graphicsScene = qobject_cast<TimelineGraphicsScene *>(scene()); + if (event->modifiers().testFlag(Qt::ControlModifier) && graphicsScene) { // TODO: Timeline bar animation is set as loop range. Select shortcut for this QDS-4941 + if (auto *current = currentItem()->asTimelineBarItem()) { + qreal left = qRound(current->mapFromSceneToFrame(current->rect().left())); + qreal right = qRound(current->mapFromSceneToFrame(current->rect().right())); + const QList<qreal> positions = {left, right}; + graphicsScene->layoutRuler()->extendPlaybackLoop(positions, event->modifiers().testFlag(Qt::ShiftModifier)); + } + } + if (auto *current = currentItem()->asTimelineKeyframeItem()) { const qreal sourceFrame = qRound(current->mapFromSceneToFrame(current->rect().center().x())); const qreal targetFrame = qRound(current->mapFromSceneToFrame(event->scenePos().x())); m_pressKeyframeDelta = targetFrame - sourceFrame; + + if (event->modifiers().testFlag(Qt::ControlModifier) && graphicsScene) { + const QList<qreal> positions = {sourceFrame}; + graphicsScene->layoutRuler()->extendPlaybackLoop(positions, false); + } } } diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp index 9176e6ef9d..ae80fa3069 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp @@ -638,12 +638,23 @@ qreal TimelineRulerSectionItem::endFrame() const return m_end; } +qreal TimelineRulerSectionItem::playbackLoopStart() const +{ + return m_playbackLoopStart > m_playbackLoopEnd ? m_playbackLoopEnd : m_playbackLoopStart; +} + +qreal TimelineRulerSectionItem::playbackLoopEnd() const +{ + return m_playbackLoopEnd < m_playbackLoopStart ? m_playbackLoopStart : m_playbackLoopEnd; +} + void TimelineRulerSectionItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { static const QColor backgroundColor = Theme::getColor(Theme::DScontrolBackground); static const QColor penColor = Theme::getColor(Theme::PanelTextColorLight); static const QColor highlightColor = Theme::instance()->Theme::qmlDesignerButtonColor(); static const QColor handleColor = Theme::getColor(Theme::QmlDesigner_HighlightColor); + static const QColor playbackLoopColor = QColor(0, 255, 0, 255); // TODO: Timeline looping range color. Select color for this QDS-4941 const int scrollOffset = TimelineGraphicsScene::getScrollOffset(scene()); @@ -685,15 +696,22 @@ void TimelineRulerSectionItem::paint(QPainter *painter, const QStyleOptionGraphi painter->setPen(penColor); const int height = size().height() - 1; - - - drawLine(painter, TimelineConstants::sectionWidth + scrollOffset - TimelineConstants::timelineLeftOffset, height, size().width() + scrollOffset, height); + if (m_playbackLoopEnabled) { + painter->setPen(playbackLoopColor); + drawLine(painter, + TimelineConstants::sectionWidth + (playbackLoopStart() * m_scaling), + height, + TimelineConstants::sectionWidth + (playbackLoopEnd() * m_scaling), + height); + } + + painter->setPen(penColor); QFont font = painter->font(); font.setPixelSize(8); @@ -775,9 +793,112 @@ qreal TimelineRulerSectionItem::getFrameTick() const return m_frameTick; } +void TimelineRulerSectionItem::setPlaybackLoopEnabled(bool value) +{ + m_playbackLoopEnabled = value; + if (m_playbackLoopStart == m_playbackLoopEnd) { + m_playbackLoopStart = 0.; + m_playbackLoopEnd = m_duration; + } + update(); +} + +void TimelineRulerSectionItem::setPlaybackLoopTimes(float startFrame, float endFrame) +{ + if (m_playbackLoopEnabled) { + startFrame = startFrame; + endFrame = endFrame; + m_playbackLoopStart = startFrame > m_duration ? m_duration : startFrame < 0.0 ? 0.0 : startFrame; + m_playbackLoopEnd = endFrame > m_duration ? m_duration : endFrame < 0.0 ? 0.0 : endFrame; + emit playbackLoopValuesChanged(); + update(); + } +} + +void TimelineRulerSectionItem::extendPlaybackLoop(const QList<qreal> &positions, bool reset) +{ + if (m_playbackLoopEnabled) { + qreal originalLeft, left = m_playbackLoopStart; + qreal originalRight, right = m_playbackLoopEnd; + + if (reset) { + if (positions.count() >= 2) { + left = m_duration; + right = 0; + } else { + return; + } + } + + for (auto pos : positions) { + qreal mapPos = pos; + right = mapPos > right ? mapPos : right; + left = mapPos < left ? mapPos : left; + } + + if (left != originalLeft && right != originalRight && (left != right)) + setPlaybackLoopTimes(left, right); + } +} + +void TimelineRulerSectionItem::updatePlaybackLoop(QGraphicsSceneMouseEvent *event) +{ + if (m_playbackLoopEnabled && event->modifiers().testFlag(Qt::ControlModifier)) { // Placeholder modifier + QPointF pos = event->scenePos(); + TimelineGraphicsScene *graphicsScene = timelineScene(); + + qreal x = (qRound(pos.x()) - TimelineConstants::sectionWidth + graphicsScene->getScrollOffset(scene()) + - TimelineConstants::timelineLeftOffset) / m_scaling; + x = x > m_duration ? m_duration : x; + x = x < 0.0 ? 0.0 : x; + if (event->modifiers().testFlag(Qt::ShiftModifier)) { // Placeholder modifier + // snap to ruler markers + x = graphicsScene->snap(x / m_scaling) * m_scaling; + } + + bool nearStart = abs(m_playbackLoopStart - x) < m_frameTick; + bool nearEnd = abs(m_playbackLoopEnd - x) < m_frameTick; + + int Type = event->type(); + if (Type == QEvent::GraphicsSceneMousePress) { + if (nearStart) { + m_isMovingPlaybackStart = true; + } else { + if (!nearEnd) + m_playbackLoopStart = x; + m_playbackLoopEnd = x; + m_isPaintingPlaybackLoopRange = true; + } + emit playbackLoopValuesChanged(); + update(); + } else if (Type == QEvent::GraphicsSceneMouseMove) { + if (!m_isPaintingPlaybackLoopRange && (nearStart || nearEnd)) { + if (cursor().shape() != Qt::SizeHorCursor) + setCursor(QCursor(Qt::SizeHorCursor)); + } else if (cursor().shape() != Qt::ArrowCursor) { + setCursor(QCursor(Qt::ArrowCursor)); + } + if (m_isMovingPlaybackStart) { + m_playbackLoopStart = x; + emit playbackLoopValuesChanged(); + update(); + } else if (m_isPaintingPlaybackLoopRange){ + m_playbackLoopEnd = x; + emit playbackLoopValuesChanged(); + update(); + } + } else if (Type == QEvent::GraphicsSceneMouseRelease) { + m_isPaintingPlaybackLoopRange = m_isMovingPlaybackStart = false; + } + } else if (cursor().shape() != Qt::ArrowCursor) { + setCursor(QCursor(Qt::ArrowCursor)); + } +} + void TimelineRulerSectionItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { TimelineItem::mousePressEvent(event); + updatePlaybackLoop(event); emit rulerClicked(event->pos()); } @@ -869,6 +990,11 @@ bool TimelineBarItem::isLocked() const return sectionItem()->targetNode().isValid() && sectionItem()->targetNode().locked(); } +TimelineBarItem *TimelineBarItem::asTimelineBarItem() +{ + return this; +} + void TimelineBarItem::scrollOffsetChanged() { sectionItem()->invalidateBar(); diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h index 956ef31ef7..812f1159e2 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h @@ -52,6 +52,7 @@ public: bool isLocked() const override; + TimelineBarItem *asTimelineBarItem() override; protected: void scrollOffsetChanged() override; void paint(QPainter *painter, @@ -148,7 +149,7 @@ class TimelineRulerSectionItem : public TimelineItem signals: void rulerClicked(const QPointF &pos); - + void playbackLoopValuesChanged(); void zoomChanged(int zoom); public: @@ -167,10 +168,17 @@ public: qreal durationViewportLength() const; qreal startFrame() const; qreal endFrame() const; + qreal playbackLoopStart() const; + qreal playbackLoopEnd() const; QComboBox *comboBox() const; void setSizeHints(int width); + void setPlaybackLoopEnabled(bool value); + void setPlaybackLoopTimes(float start, float end); + void extendPlaybackLoop(const QList<qreal> &positions, bool reset); + void updatePlaybackLoop(QGraphicsSceneMouseEvent *event); + signals: void addTimelineClicked(); @@ -194,6 +202,11 @@ private: 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 + qreal m_playbackLoopStart = 0.; + qreal m_playbackLoopEnd = 0.; + bool m_playbackLoopEnabled = false; + bool m_isPaintingPlaybackLoopRange = false; + bool m_isMovingPlaybackStart = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp index 5dab65a27f..753ce4e251 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp @@ -162,6 +162,7 @@ void TimelineSelectionTool::resetHighlights() void TimelineSelectionTool::aboutToSelect(SelectionMode mode, QList<QGraphicsItem *> items) { resetHighlights(); + m_playbackLoopTimeSteps.clear(); for (auto *item : items) { if (auto *keyframe = TimelineMovableAbstractItem::asTimelineKeyframeItem(item)) { @@ -179,15 +180,38 @@ void TimelineSelectionTool::aboutToSelect(SelectionMode mode, QList<QGraphicsIte else keyframe->setHighlighted(true); + if (mode == SelectionMode::Toggle || mode == SelectionMode::Add) // TODO: Timeline keyframe highlight with selection tool is set as or added to loop range. Select shortcut for this QDS-4941 + m_playbackLoopTimeSteps << keyframe->mapFromSceneToFrame((keyframe->rect().center() + item->scenePos()).x()); + m_aboutToSelectBuffer << keyframe; + } else if (auto *barItem = TimelineMovableAbstractItem::asTimelineBarItem(item)) { + QRectF rect = barItem->rect(); + QPointF center = rect.center() + item->scenePos(); + QPointF left = QPointF(rect.left(), 0) + item->scenePos(); + QPointF right = QPointF(rect.right(), 0) + item->scenePos(); + if (mode == SelectionMode::Add) { // TODO: Timeline bar item highlight with selection tool is added to loop range. Select shortcut for this QDS-4941 + if (m_selectionRect->rect().contains(left)) + m_playbackLoopTimeSteps << barItem->mapFromSceneToFrame(left.x()); + if (m_selectionRect->rect().contains(right)) + m_playbackLoopTimeSteps << barItem->mapFromSceneToFrame(right.x()); + if (m_selectionRect->rect().contains(center)) + m_playbackLoopTimeSteps << barItem->mapFromSceneToFrame(center.x()); + } else if (mode == SelectionMode::Toggle && m_selectionRect->rect().contains(center)) { // TODO: Timeline bar item highlight with selection tool is set as loop range. Select shortcut for this QDS-4941 + m_playbackLoopTimeSteps << barItem->mapFromSceneToFrame(left.x()); + m_playbackLoopTimeSteps << barItem->mapFromSceneToFrame(right.x()); + } } } } void TimelineSelectionTool::commitSelection(SelectionMode mode) { + if (m_playbackLoopTimeSteps.count()) + qobject_cast<TimelineGraphicsScene *>(scene())->layoutRuler()->extendPlaybackLoop(m_playbackLoopTimeSteps, + mode == SelectionMode::Toggle); // TODO: Highlighting items with selection tool is set or added to loop range. Select shortcut for this QDS-4941 scene()->selectKeyframes(mode, m_aboutToSelectBuffer); m_aboutToSelectBuffer.clear(); + m_playbackLoopTimeSteps.clear(); } } // End namespace QmlDesigner. diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h index f635ea0530..ec4ba1f148 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h @@ -37,7 +37,7 @@ namespace QmlDesigner { class TimelineToolDelegate; class TimelineKeyframeItem; - +class TimelineBarItem; class TimelineGraphicsScene; enum class SelectionMode { New, Add, Remove, Toggle }; @@ -81,6 +81,7 @@ private: QGraphicsRectItem *m_selectionRect; QList<TimelineKeyframeItem *> m_aboutToSelectBuffer; + QList<qreal> m_playbackLoopTimeSteps; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp index 9c92a8e36e..2d6bac7ff0 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp @@ -56,6 +56,20 @@ namespace QmlDesigner { +class LineEditDoubleValidator : public QDoubleValidator +{ +public: + LineEditDoubleValidator(double bottom, double top, int decimals, QLineEdit *parent) + : QDoubleValidator(bottom, top, decimals, parent), + m_value(1.0) { parent->setText(locale().toString(1.0, 'f', 1)); } + ~LineEditDoubleValidator() {}; + + void fixup(QString &input) const override { input = locale().toString(m_value, 'f', 1); }; + void setValue(double value) { m_value = value; }; +private: + double m_value; +}; + static bool isSpacer(QObject *object) { return object->property("spacer_widget").toBool(); @@ -189,6 +203,11 @@ void TimelineToolBar::setScaleFactor(int factor) m_scale->setValue(factor); } +void TimelineToolBar::setPlayState(bool state) +{ + m_playing->setChecked(state); +} + void TimelineToolBar::setActionEnabled(const QString &name, bool enabled) { for (auto *action : actions()) @@ -277,14 +296,15 @@ void TimelineToolBar::createCenterControls() addAction(previous); addSpacing(2); - - auto *play = createAction(TimelineConstants::C_PLAY, - TimelineIcons::START_PLAYBACK.icon(), - tr("Play"), - QKeySequence(Qt::Key_Space)); - - connect(play, &QAction::triggered, this, &TimelineToolBar::playTriggered); - addAction(play); + QIcon playbackIcon = TimelineUtils::mergeIcons(TimelineIcons::PAUSE_PLAYBACK, + TimelineIcons::START_PLAYBACK); + m_playing = createAction(TimelineConstants::C_PLAY, + playbackIcon, + tr("Play"), + QKeySequence(Qt::Key_Space)); + m_playing->setCheckable(true); + connect(m_playing, &QAction::triggered, this, &TimelineToolBar::playTriggered); + addAction(m_playing); addSpacing(2); @@ -306,13 +326,40 @@ void TimelineToolBar::createCenterControls() connect(toEnd, &QAction::triggered, this, &TimelineToolBar::toLastFrameTriggered); addAction(toEnd); -#if 0 - auto *loop = new QAction(TimelineIcons::LOOP_PLAYBACK.icon(), tr("Loop"), this); - addAction(loop); -#endif + addSpacing(10); + + addSeparator(); + + addSpacing(10); + + auto *loopAnimation = createAction(TimelineConstants::C_LOOP_PLAYBACK, + TimelineIcons::LOOP_PLAYBACK.icon(), + tr("Loop Playback"), + QKeySequence((Qt::ControlModifier | Qt::ShiftModifier) + Qt::Key_Space)); // TODO: Toggles looping. Select shortcut for this QDS-4941 + + loopAnimation->setCheckable(true); + connect(loopAnimation, &QAction::toggled, [&](bool value) { emit loopPlaybackToggled(value);} ); + + addAction(loopAnimation); addSpacing(5); + m_animationPlaybackSpeed = createToolBarLineEdit(this); + LineEditDoubleValidator *validator = new LineEditDoubleValidator(0.1, 100.0, 1, m_animationPlaybackSpeed); + m_animationPlaybackSpeed->setValidator(validator); + m_animationPlaybackSpeed->setToolTip(tr("Playback Speed")); + addWidget(m_animationPlaybackSpeed); + + auto emitPlaybackSpeedChanged = [this, validator]() { + bool ok = false; + if (double res = validator->locale().toDouble(m_animationPlaybackSpeed->text(), &ok); ok==true) { + validator->setValue(res); + m_animationPlaybackSpeed->setText(locale().toString(res, 'f', 1)); + emit playbackSpeedChanged(res); + } + }; + connect(m_animationPlaybackSpeed, &QLineEdit::editingFinished, emitPlaybackSpeedChanged); + addSeparator(); m_currentFrame = createToolBarLineEdit(this); diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h index 8719534992..52afe6842d 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.h @@ -58,6 +58,7 @@ signals: void recordToggled(bool val); void loopPlaybackToggled(bool val); + void playbackSpeedChanged(float val); void scaleFactorChanged(int value); void startFrameChanged(int value); @@ -80,6 +81,7 @@ public: void setCurrentFrame(qreal frame); void setEndFrame(qreal frame); void setScaleFactor(int factor); + void setPlayState(bool state); void setActionEnabled(const QString &name, bool enabled); void removeTimeline(const QmlTimeline &timeline); @@ -102,7 +104,9 @@ private: QLineEdit *m_firstFrame = nullptr; QLineEdit *m_currentFrame = nullptr; QLineEdit *m_lastFrame = nullptr; + QLineEdit *m_animationPlaybackSpeed = nullptr; + QAction *m_playing = nullptr; QAction *m_recording = nullptr; bool m_blockReflection = false; }; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp index b71173d4a1..0df28ee597 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp @@ -60,6 +60,7 @@ #include <QVBoxLayout> #include <QtGlobal> #include <QSpacerItem> +#include <QVariantAnimation> #include <cmath> @@ -123,6 +124,9 @@ TimelineWidget::TimelineWidget(TimelineView *view) , m_graphicsScene(new TimelineGraphicsScene(this)) , m_addButton(new QPushButton(this)) , m_onboardingContainer(new QWidget(this)) + , m_loopPlayback(false) + , m_playbackSpeed(1) + , m_playbackAnimation(new QVariantAnimation(this)) { setWindowTitle(tr("Timeline", "Title of timeline view")); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -277,6 +281,23 @@ TimelineWidget::TimelineWidget(TimelineView *view) m_graphicsScene->setZoom(std::clamp(m_graphicsScene->zoom() + s, 0, 100), ps); }); installEventFilter(filter); + + m_playbackAnimation->stop(); + auto playAnimation = [this](QVariant frame) { graphicsScene()->setCurrentFrame(qRound(frame.toDouble())); }; + connect(m_playbackAnimation, &QVariantAnimation::valueChanged, playAnimation); + + auto updatePlaybackLoopValues = [this]() { + updatePlaybackValues(); + }; + connect(graphicsScene()->layoutRuler(), &TimelineRulerSectionItem::playbackLoopValuesChanged, updatePlaybackLoopValues); + + auto setPlaybackState = [this](QAbstractAnimation::State newState, QAbstractAnimation::State oldState) { + m_toolbar->setPlayState(newState == QAbstractAnimation::State::Running); + }; + connect(m_playbackAnimation, &QVariantAnimation::stateChanged, setPlaybackState); + + auto onFinish = [this]() { graphicsScene()->setCurrentFrame(m_playbackAnimation->startValue().toInt()); }; + connect(m_playbackAnimation, &QVariantAnimation::finished, onFinish); } void TimelineWidget::connectToolbar() @@ -320,6 +341,21 @@ void TimelineWidget::connectToolbar() connect(m_toolbar, &TimelineToolBar::recordToggled, this, &TimelineWidget::setTimelineRecording); + connect(m_toolbar, &TimelineToolBar::playTriggered, this, &TimelineWidget::toggleAnimationPlayback); + + auto setLoopAnimation = [this](bool loop) { + graphicsScene()->layoutRuler()->setPlaybackLoopEnabled(loop); + if (m_playbackAnimation->state() == QAbstractAnimation::Running) + m_playbackAnimation->pause(); + m_loopPlayback = loop; + }; + connect(m_toolbar, &TimelineToolBar::loopPlaybackToggled, setLoopAnimation); + auto setPlaybackSpeed = [this](float val) { + m_playbackSpeed = val; + updatePlaybackValues(); + }; + connect(m_toolbar, &TimelineToolBar::playbackSpeedChanged, setPlaybackSpeed); + connect(m_toolbar, &TimelineToolBar::openEasingCurveEditor, this, @@ -329,19 +365,6 @@ void TimelineWidget::connectToolbar() &TimelineToolBar::settingDialogClicked, m_timelineView, &TimelineView::openSettingsDialog); - - for (auto action : QmlDesignerPlugin::instance()->designerActionManager().designerActions()) { - if (action->menuId() == "LivePreview") { - QObject::connect(m_toolbar, - &TimelineToolBar::playTriggered, - action->action(), - [action]() { - action->action()->setChecked(false); - action->action()->triggered(true); - }); - } - } - setTimelineActive(false); } @@ -383,6 +406,55 @@ void TimelineWidget::openEasingCurveEditor() } } +void TimelineWidget::updatePlaybackValues() +{ + QmlTimeline currentTimeline = graphicsScene()->currentTimeline(); + qreal endFrame = currentTimeline.endKeyframe(); + qreal startFrame = currentTimeline.startKeyframe(); + qreal duration = currentTimeline.duration(); + + if (m_loopPlayback) { + m_playbackAnimation->setLoopCount(-1); + qreal loopStart = graphicsScene()->layoutRuler()->playbackLoopStart(); + qreal loopEnd = graphicsScene()->layoutRuler()->playbackLoopEnd(); + startFrame = qRound(startFrame + loopStart); + endFrame = qRound(startFrame + (loopEnd - loopStart)); + duration = endFrame - startFrame; + } else { + m_playbackAnimation->setLoopCount(1); + } + + if (duration > 0.0f) { + qreal animationDuration = duration * (1.0 / m_playbackSpeed); + m_playbackAnimation->setDuration(animationDuration); + } else { + if (m_playbackAnimation->state() == QAbstractAnimation::Running) + m_playbackAnimation->stop(); + } + qreal a = m_playbackAnimation->duration(); + qreal newCurrentTime = (currentTimeline.currentKeyframe() - startFrame) * (1.0 / m_playbackSpeed); + if (qRound(m_playbackAnimation->startValue().toDouble()) != qRound(startFrame) + || qRound(m_playbackAnimation->endValue().toDouble()) != qRound(endFrame)) { + newCurrentTime = 0; + } + m_playbackAnimation->setStartValue(qRound(startFrame)); + m_playbackAnimation->setEndValue(qRound(endFrame)); + m_playbackAnimation->setCurrentTime(newCurrentTime); +} + +void TimelineWidget::toggleAnimationPlayback() +{ + QmlTimeline currentTimeline = graphicsScene()->currentTimeline(); + if (currentTimeline.isValid() && m_playbackSpeed > 0.0) { + if (m_playbackAnimation->state() == QAbstractAnimation::Running) { + m_playbackAnimation->pause(); + } else { + updatePlaybackValues(); + m_playbackAnimation->start(); + } + } +} + void TimelineWidget::setTimelineRecording(bool value) { ModelNode node = timelineView()->modelNodeForId(m_toolbar->currentTimelineId()); diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h index b1e24caf93..3fa3db1abf 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h @@ -39,6 +39,7 @@ QT_FORWARD_DECLARE_CLASS(QResizeEvent) QT_FORWARD_DECLARE_CLASS(QShowEvent) QT_FORWARD_DECLARE_CLASS(QString) QT_FORWARD_DECLARE_CLASS(QPushButton) +QT_FORWARD_DECLARE_CLASS(QVariantAnimation) namespace QmlDesigner { @@ -74,9 +75,11 @@ public: public slots: void selectionChanged(); void openEasingCurveEditor(); + void toggleAnimationPlayback(); void setTimelineRecording(bool value); void changeScaleFactor(int factor); void scroll(const TimelineUtils::Side &side); + void updatePlaybackValues(); protected: void showEvent(QShowEvent *event) override; @@ -105,6 +108,10 @@ private: QPushButton *m_addButton = nullptr; QWidget *m_onboardingContainer = nullptr; + + bool m_loopPlayback; + qreal m_playbackSpeed; + QVariantAnimation *m_playbackAnimation; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp index 7bea0b07bd..ab82744cc6 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp @@ -425,6 +425,11 @@ void TransitionEditorGraphicsScene::invalidateSections() invalidateLayout(); } +TimelineRulerSectionItem *TransitionEditorGraphicsScene::layoutRuler() const +{ + return m_layout->ruler(); +} + TransitionEditorView *TransitionEditorGraphicsScene::transitionEditorView() const { return m_parent->transitionEditorView(); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h index d4e91cdd6c..5efbf2e675 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h @@ -72,6 +72,7 @@ public: void invalidateLayout(); void setDuration(int duration); + TimelineRulerSectionItem *layoutRuler() const; TransitionEditorView *transitionEditorView() const; TransitionEditorWidget *transitionEditorWidget() const; TransitionEditorToolBar *toolBar() const; |