diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h b/src/plugins/qmldesigner/components/timelineeditor/timelineconstants.h index 622dc366edb..54119db9fd4 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 0b67542fc6f..704d7c2b0d0 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 ade4edfeb8d..b1836393269 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 1b0b2cb7e53..a5121b39bb7 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(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 d79101b4d9d..b38406cb788 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 &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 0508606c11f..82c84abeb60 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(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 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 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 9176e6ef9d1..ae80fa3069b 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 &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 956ef31ef78..812f1159e24 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 &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 5dab65a27f4..753ce4e251f 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 items) { resetHighlights(); + m_playbackLoopTimeSteps.clear(); for (auto *item : items) { if (auto *keyframe = TimelineMovableAbstractItem::asTimelineKeyframeItem(item)) { @@ -179,15 +180,38 @@ void TimelineSelectionTool::aboutToSelect(SelectionMode mode, QListsetHighlighted(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(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 f635ea0530a..ec4ba1f1483 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 m_aboutToSelectBuffer; + QList m_playbackLoopTimeSteps; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp index 9c92a8e36ee..2d6bac7ff0f 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 87195349921..52afe6842d6 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 b71173d4a1f..0df28ee597d 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include @@ -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 b1e24caf937..3fa3db1abf9 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 7bea0b07bd8..ab82744cc67 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 d4e91cdd6ca..5efbf2e6756 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;