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>
This commit is contained in:
Tony Leinonen
2021-08-10 16:19:43 +03:00
parent b485890b19
commit f7489252df
16 changed files with 378 additions and 30 deletions

View File

@@ -64,6 +64,7 @@ const char C_TO_START[] = "QmlDesigner.ToStart";
const char C_TO_END[] = "QmlDesigner.ToEnd"; const char C_TO_END[] = "QmlDesigner.ToEnd";
const char C_PREVIOUS[] = "QmlDesigner.Previous"; const char C_PREVIOUS[] = "QmlDesigner.Previous";
const char C_PLAY[] = "QmlDesigner.Play"; const char C_PLAY[] = "QmlDesigner.Play";
const char C_LOOP_PLAYBACK[] = "QmlDesigner.LoopPlayback";
const char C_NEXT[] = "QmlDesigner.Next"; const char C_NEXT[] = "QmlDesigner.Next";
const char C_AUTO_KEYFRAME[] = "QmlDesigner.AutoKeyframe"; const char C_AUTO_KEYFRAME[] = "QmlDesigner.AutoKeyframe";
const char C_CURVE_PICKER[] = "QmlDesigner.CurvePicker"; const char C_CURVE_PICKER[] = "QmlDesigner.CurvePicker";

View File

@@ -598,6 +598,9 @@ void TimelineGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
void TimelineGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void TimelineGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{ {
auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos())); auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
m_layout->ruler()->updatePlaybackLoop(event);
m_tools.mouseMoveEvent(topItem, event); m_tools.mouseMoveEvent(topItem, event);
QGraphicsScene::mouseMoveEvent(event); QGraphicsScene::mouseMoveEvent(event);
} }
@@ -605,6 +608,10 @@ void TimelineGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void TimelineGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) void TimelineGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{ {
auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos())); auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
m_layout->ruler()->updatePlaybackLoop(event);
/* The tool has handle the event last. */ /* The tool has handle the event last. */
QGraphicsScene::mouseReleaseEvent(event); QGraphicsScene::mouseReleaseEvent(event);
m_tools.mouseReleaseEvent(topItem, event); m_tools.mouseReleaseEvent(topItem, event);
@@ -695,6 +702,11 @@ void TimelineGraphicsScene::invalidateSections()
invalidateLayout(); invalidateLayout();
} }
TimelineRulerSectionItem *TimelineGraphicsScene::layoutRuler() const
{
return m_layout->ruler();
}
TimelineView *TimelineGraphicsScene::timelineView() const TimelineView *TimelineGraphicsScene::timelineView() const
{ {
return m_parent->timelineView(); return m_parent->timelineView();

View File

@@ -130,6 +130,7 @@ public:
void setStartFrame(int frame); void setStartFrame(int frame);
void setEndFrame(int frame); void setEndFrame(int frame);
TimelineRulerSectionItem *layoutRuler() const;
TimelineView *timelineView() const; TimelineView *timelineView() const;
TimelineWidget *timelineWidget() const; TimelineWidget *timelineWidget() const;
TimelineToolBar *toolBar() const; TimelineToolBar *toolBar() const;

View File

@@ -131,6 +131,16 @@ TimelineKeyframeItem *TimelineMovableAbstractItem::asTimelineKeyframeItem(QGraph
return nullptr; return nullptr;
} }
TimelineBarItem *TimelineMovableAbstractItem::asTimelineBarItem(QGraphicsItem *item)
{
auto movableItem = TimelineMovableAbstractItem::cast(item);
if (movableItem)
return movableItem->asTimelineBarItem();
return nullptr;
}
qreal TimelineMovableAbstractItem::rulerScaling() const qreal TimelineMovableAbstractItem::rulerScaling() const
{ {
return qobject_cast<AbstractScrollGraphicsScene *>(scene())->rulerScaling(); return qobject_cast<AbstractScrollGraphicsScene *>(scene())->rulerScaling();
@@ -156,6 +166,12 @@ TimelineFrameHandle *TimelineMovableAbstractItem::asTimelineFrameHandle()
return nullptr; return nullptr;
} }
TimelineBarItem *TimelineMovableAbstractItem::asTimelineBarItem()
{
return nullptr;
}
bool TimelineMovableAbstractItem::isLocked() const bool TimelineMovableAbstractItem::isLocked() const
{ {
return false; return false;

View File

@@ -35,6 +35,7 @@ namespace QmlDesigner {
class AbstractScrollGraphicsScene; class AbstractScrollGraphicsScene;
class TimelineKeyframeItem; class TimelineKeyframeItem;
class TimelineFrameHandle; class TimelineFrameHandle;
class TimelineBarItem;
class TimelineMovableAbstractItem : public QGraphicsRectItem class TimelineMovableAbstractItem : public QGraphicsRectItem
{ {
@@ -51,6 +52,7 @@ public:
static TimelineMovableAbstractItem *topMoveableItem(const QList<QGraphicsItem *> &items); static TimelineMovableAbstractItem *topMoveableItem(const QList<QGraphicsItem *> &items);
static void emitScrollOffsetChanged(QGraphicsItem *item); static void emitScrollOffsetChanged(QGraphicsItem *item);
static TimelineKeyframeItem *asTimelineKeyframeItem(QGraphicsItem *item); static TimelineKeyframeItem *asTimelineKeyframeItem(QGraphicsItem *item);
static TimelineBarItem *asTimelineBarItem(QGraphicsItem *item);
qreal rulerScaling() const; qreal rulerScaling() const;
@@ -68,6 +70,7 @@ public:
virtual TimelineKeyframeItem *asTimelineKeyframeItem(); virtual TimelineKeyframeItem *asTimelineKeyframeItem();
virtual TimelineFrameHandle *asTimelineFrameHandle(); virtual TimelineFrameHandle *asTimelineFrameHandle();
virtual TimelineBarItem *asTimelineBarItem();
virtual bool isLocked() const; virtual bool isLocked() const;

View File

@@ -73,10 +73,25 @@ void TimelineMoveTool::mousePressEvent(TimelineMovableAbstractItem *item,
if (currentItem() && currentItem()->isLocked()) if (currentItem() && currentItem()->isLocked())
return; 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()) { if (auto *current = currentItem()->asTimelineKeyframeItem()) {
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())); const qreal targetFrame = qRound(current->mapFromSceneToFrame(event->scenePos().x()));
m_pressKeyframeDelta = targetFrame - sourceFrame; m_pressKeyframeDelta = targetFrame - sourceFrame;
if (event->modifiers().testFlag(Qt::ControlModifier) && graphicsScene) {
const QList<qreal> positions = {sourceFrame};
graphicsScene->layoutRuler()->extendPlaybackLoop(positions, false);
}
} }
} }

View File

@@ -638,12 +638,23 @@ qreal TimelineRulerSectionItem::endFrame() const
return m_end; 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 *) void TimelineRulerSectionItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{ {
static const QColor backgroundColor = Theme::getColor(Theme::DScontrolBackground); static const QColor backgroundColor = Theme::getColor(Theme::DScontrolBackground);
static const QColor penColor = Theme::getColor(Theme::PanelTextColorLight); static const QColor penColor = Theme::getColor(Theme::PanelTextColorLight);
static const QColor highlightColor = Theme::instance()->Theme::qmlDesignerButtonColor(); static const QColor highlightColor = Theme::instance()->Theme::qmlDesignerButtonColor();
static const QColor handleColor = Theme::getColor(Theme::QmlDesigner_HighlightColor); 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()); const int scrollOffset = TimelineGraphicsScene::getScrollOffset(scene());
@@ -685,15 +696,22 @@ void TimelineRulerSectionItem::paint(QPainter *painter, const QStyleOptionGraphi
painter->setPen(penColor); painter->setPen(penColor);
const int height = size().height() - 1; const int height = size().height() - 1;
drawLine(painter, drawLine(painter,
TimelineConstants::sectionWidth + scrollOffset TimelineConstants::sectionWidth + scrollOffset
- TimelineConstants::timelineLeftOffset, - TimelineConstants::timelineLeftOffset,
height, height,
size().width() + scrollOffset, size().width() + scrollOffset,
height); 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(); QFont font = painter->font();
font.setPixelSize(8); font.setPixelSize(8);
@@ -775,9 +793,112 @@ qreal TimelineRulerSectionItem::getFrameTick() const
return m_frameTick; 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) void TimelineRulerSectionItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{ {
TimelineItem::mousePressEvent(event); TimelineItem::mousePressEvent(event);
updatePlaybackLoop(event);
emit rulerClicked(event->pos()); emit rulerClicked(event->pos());
} }
@@ -869,6 +990,11 @@ bool TimelineBarItem::isLocked() const
return sectionItem()->targetNode().isValid() && sectionItem()->targetNode().locked(); return sectionItem()->targetNode().isValid() && sectionItem()->targetNode().locked();
} }
TimelineBarItem *TimelineBarItem::asTimelineBarItem()
{
return this;
}
void TimelineBarItem::scrollOffsetChanged() void TimelineBarItem::scrollOffsetChanged()
{ {
sectionItem()->invalidateBar(); sectionItem()->invalidateBar();

View File

@@ -52,6 +52,7 @@ public:
bool isLocked() const override; bool isLocked() const override;
TimelineBarItem *asTimelineBarItem() override;
protected: protected:
void scrollOffsetChanged() override; void scrollOffsetChanged() override;
void paint(QPainter *painter, void paint(QPainter *painter,
@@ -148,7 +149,7 @@ class TimelineRulerSectionItem : public TimelineItem
signals: signals:
void rulerClicked(const QPointF &pos); void rulerClicked(const QPointF &pos);
void playbackLoopValuesChanged();
void zoomChanged(int zoom); void zoomChanged(int zoom);
public: public:
@@ -167,10 +168,17 @@ public:
qreal durationViewportLength() const; qreal durationViewportLength() const;
qreal startFrame() const; qreal startFrame() const;
qreal endFrame() const; qreal endFrame() const;
qreal playbackLoopStart() const;
qreal playbackLoopEnd() const;
QComboBox *comboBox() const; QComboBox *comboBox() const;
void setSizeHints(int width); 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: signals:
void addTimelineClicked(); void addTimelineClicked();
@@ -194,6 +202,11 @@ private:
qreal m_end = 0; qreal m_end = 0;
qreal m_scaling = 1; qreal m_scaling = 1;
qreal m_frameTick = 1.; // distance between 2 tick steps (in frames) on the ruler at current scale 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 } // namespace QmlDesigner

View File

@@ -162,6 +162,7 @@ void TimelineSelectionTool::resetHighlights()
void TimelineSelectionTool::aboutToSelect(SelectionMode mode, QList<QGraphicsItem *> items) void TimelineSelectionTool::aboutToSelect(SelectionMode mode, QList<QGraphicsItem *> items)
{ {
resetHighlights(); resetHighlights();
m_playbackLoopTimeSteps.clear();
for (auto *item : items) { for (auto *item : items) {
if (auto *keyframe = TimelineMovableAbstractItem::asTimelineKeyframeItem(item)) { if (auto *keyframe = TimelineMovableAbstractItem::asTimelineKeyframeItem(item)) {
@@ -179,15 +180,38 @@ void TimelineSelectionTool::aboutToSelect(SelectionMode mode, QList<QGraphicsIte
else else
keyframe->setHighlighted(true); 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; 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) 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); scene()->selectKeyframes(mode, m_aboutToSelectBuffer);
m_aboutToSelectBuffer.clear(); m_aboutToSelectBuffer.clear();
m_playbackLoopTimeSteps.clear();
} }
} // End namespace QmlDesigner. } // End namespace QmlDesigner.

View File

@@ -37,7 +37,7 @@ namespace QmlDesigner {
class TimelineToolDelegate; class TimelineToolDelegate;
class TimelineKeyframeItem; class TimelineKeyframeItem;
class TimelineBarItem;
class TimelineGraphicsScene; class TimelineGraphicsScene;
enum class SelectionMode { New, Add, Remove, Toggle }; enum class SelectionMode { New, Add, Remove, Toggle };
@@ -81,6 +81,7 @@ private:
QGraphicsRectItem *m_selectionRect; QGraphicsRectItem *m_selectionRect;
QList<TimelineKeyframeItem *> m_aboutToSelectBuffer; QList<TimelineKeyframeItem *> m_aboutToSelectBuffer;
QList<qreal> m_playbackLoopTimeSteps;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -56,6 +56,20 @@
namespace QmlDesigner { 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) static bool isSpacer(QObject *object)
{ {
return object->property("spacer_widget").toBool(); return object->property("spacer_widget").toBool();
@@ -189,6 +203,11 @@ void TimelineToolBar::setScaleFactor(int factor)
m_scale->setValue(factor); m_scale->setValue(factor);
} }
void TimelineToolBar::setPlayState(bool state)
{
m_playing->setChecked(state);
}
void TimelineToolBar::setActionEnabled(const QString &name, bool enabled) void TimelineToolBar::setActionEnabled(const QString &name, bool enabled)
{ {
for (auto *action : actions()) for (auto *action : actions())
@@ -277,14 +296,15 @@ void TimelineToolBar::createCenterControls()
addAction(previous); addAction(previous);
addSpacing(2); addSpacing(2);
QIcon playbackIcon = TimelineUtils::mergeIcons(TimelineIcons::PAUSE_PLAYBACK,
auto *play = createAction(TimelineConstants::C_PLAY, TimelineIcons::START_PLAYBACK);
TimelineIcons::START_PLAYBACK.icon(), m_playing = createAction(TimelineConstants::C_PLAY,
tr("Play"), playbackIcon,
QKeySequence(Qt::Key_Space)); tr("Play"),
QKeySequence(Qt::Key_Space));
connect(play, &QAction::triggered, this, &TimelineToolBar::playTriggered); m_playing->setCheckable(true);
addAction(play); connect(m_playing, &QAction::triggered, this, &TimelineToolBar::playTriggered);
addAction(m_playing);
addSpacing(2); addSpacing(2);
@@ -306,13 +326,40 @@ void TimelineToolBar::createCenterControls()
connect(toEnd, &QAction::triggered, this, &TimelineToolBar::toLastFrameTriggered); connect(toEnd, &QAction::triggered, this, &TimelineToolBar::toLastFrameTriggered);
addAction(toEnd); addAction(toEnd);
#if 0 addSpacing(10);
auto *loop = new QAction(TimelineIcons::LOOP_PLAYBACK.icon(), tr("Loop"), this);
addAction(loop); addSeparator();
#endif
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); 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(); addSeparator();
m_currentFrame = createToolBarLineEdit(this); m_currentFrame = createToolBarLineEdit(this);

View File

@@ -58,6 +58,7 @@ signals:
void recordToggled(bool val); void recordToggled(bool val);
void loopPlaybackToggled(bool val); void loopPlaybackToggled(bool val);
void playbackSpeedChanged(float val);
void scaleFactorChanged(int value); void scaleFactorChanged(int value);
void startFrameChanged(int value); void startFrameChanged(int value);
@@ -80,6 +81,7 @@ public:
void setCurrentFrame(qreal frame); void setCurrentFrame(qreal frame);
void setEndFrame(qreal frame); void setEndFrame(qreal frame);
void setScaleFactor(int factor); void setScaleFactor(int factor);
void setPlayState(bool state);
void setActionEnabled(const QString &name, bool enabled); void setActionEnabled(const QString &name, bool enabled);
void removeTimeline(const QmlTimeline &timeline); void removeTimeline(const QmlTimeline &timeline);
@@ -102,7 +104,9 @@ private:
QLineEdit *m_firstFrame = nullptr; QLineEdit *m_firstFrame = nullptr;
QLineEdit *m_currentFrame = nullptr; QLineEdit *m_currentFrame = nullptr;
QLineEdit *m_lastFrame = nullptr; QLineEdit *m_lastFrame = nullptr;
QLineEdit *m_animationPlaybackSpeed = nullptr;
QAction *m_playing = nullptr;
QAction *m_recording = nullptr; QAction *m_recording = nullptr;
bool m_blockReflection = false; bool m_blockReflection = false;
}; };

View File

@@ -60,6 +60,7 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QtGlobal> #include <QtGlobal>
#include <QSpacerItem> #include <QSpacerItem>
#include <QVariantAnimation>
#include <cmath> #include <cmath>
@@ -123,6 +124,9 @@ TimelineWidget::TimelineWidget(TimelineView *view)
, m_graphicsScene(new TimelineGraphicsScene(this)) , m_graphicsScene(new TimelineGraphicsScene(this))
, m_addButton(new QPushButton(this)) , m_addButton(new QPushButton(this))
, m_onboardingContainer(new QWidget(this)) , m_onboardingContainer(new QWidget(this))
, m_loopPlayback(false)
, m_playbackSpeed(1)
, m_playbackAnimation(new QVariantAnimation(this))
{ {
setWindowTitle(tr("Timeline", "Title of timeline view")); setWindowTitle(tr("Timeline", "Title of timeline view"));
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 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); m_graphicsScene->setZoom(std::clamp(m_graphicsScene->zoom() + s, 0, 100), ps);
}); });
installEventFilter(filter); 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() void TimelineWidget::connectToolbar()
@@ -320,6 +341,21 @@ void TimelineWidget::connectToolbar()
connect(m_toolbar, &TimelineToolBar::recordToggled, this, &TimelineWidget::setTimelineRecording); 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, connect(m_toolbar,
&TimelineToolBar::openEasingCurveEditor, &TimelineToolBar::openEasingCurveEditor,
this, this,
@@ -329,19 +365,6 @@ void TimelineWidget::connectToolbar()
&TimelineToolBar::settingDialogClicked, &TimelineToolBar::settingDialogClicked,
m_timelineView, m_timelineView,
&TimelineView::openSettingsDialog); &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); 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) void TimelineWidget::setTimelineRecording(bool value)
{ {
ModelNode node = timelineView()->modelNodeForId(m_toolbar->currentTimelineId()); ModelNode node = timelineView()->modelNodeForId(m_toolbar->currentTimelineId());

View File

@@ -39,6 +39,7 @@ QT_FORWARD_DECLARE_CLASS(QResizeEvent)
QT_FORWARD_DECLARE_CLASS(QShowEvent) QT_FORWARD_DECLARE_CLASS(QShowEvent)
QT_FORWARD_DECLARE_CLASS(QString) QT_FORWARD_DECLARE_CLASS(QString)
QT_FORWARD_DECLARE_CLASS(QPushButton) QT_FORWARD_DECLARE_CLASS(QPushButton)
QT_FORWARD_DECLARE_CLASS(QVariantAnimation)
namespace QmlDesigner { namespace QmlDesigner {
@@ -74,9 +75,11 @@ public:
public slots: public slots:
void selectionChanged(); void selectionChanged();
void openEasingCurveEditor(); void openEasingCurveEditor();
void toggleAnimationPlayback();
void setTimelineRecording(bool value); void setTimelineRecording(bool value);
void changeScaleFactor(int factor); void changeScaleFactor(int factor);
void scroll(const TimelineUtils::Side &side); void scroll(const TimelineUtils::Side &side);
void updatePlaybackValues();
protected: protected:
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
@@ -105,6 +108,10 @@ private:
QPushButton *m_addButton = nullptr; QPushButton *m_addButton = nullptr;
QWidget *m_onboardingContainer = nullptr; QWidget *m_onboardingContainer = nullptr;
bool m_loopPlayback;
qreal m_playbackSpeed;
QVariantAnimation *m_playbackAnimation;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -425,6 +425,11 @@ void TransitionEditorGraphicsScene::invalidateSections()
invalidateLayout(); invalidateLayout();
} }
TimelineRulerSectionItem *TransitionEditorGraphicsScene::layoutRuler() const
{
return m_layout->ruler();
}
TransitionEditorView *TransitionEditorGraphicsScene::transitionEditorView() const TransitionEditorView *TransitionEditorGraphicsScene::transitionEditorView() const
{ {
return m_parent->transitionEditorView(); return m_parent->transitionEditorView();

View File

@@ -72,6 +72,7 @@ public:
void invalidateLayout(); void invalidateLayout();
void setDuration(int duration); void setDuration(int duration);
TimelineRulerSectionItem *layoutRuler() const;
TransitionEditorView *transitionEditorView() const; TransitionEditorView *transitionEditorView() const;
TransitionEditorWidget *transitionEditorWidget() const; TransitionEditorWidget *transitionEditorWidget() const;
TransitionEditorToolBar *toolBar() const; TransitionEditorToolBar *toolBar() const;