forked from qt-creator/qt-creator
Add the ability to modify the animation range from the curve editor
Fix a synchronization issue between timeline and curve editor when the animation contains pinned curves Change-Id: I89dda234063259f41d662749d696f5fc8a04f9e8 Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -48,15 +48,18 @@ CurveEditor::CurveEditor(CurveEditorModel *model, QWidget *parent)
|
|||||||
splitter->setStretchFactor(1, 2);
|
splitter->setStretchFactor(1, 2);
|
||||||
|
|
||||||
auto *box = new QVBoxLayout;
|
auto *box = new QVBoxLayout;
|
||||||
box->addWidget(createToolBar());
|
box->addWidget(createToolBar(model));
|
||||||
box->addWidget(splitter);
|
box->addWidget(splitter);
|
||||||
setLayout(box);
|
setLayout(box);
|
||||||
|
|
||||||
connect(m_tree, &TreeView::treeItemLocked, m_view, &GraphicsView::setLocked);
|
|
||||||
connect(m_tree->selectionModel(), &SelectionModel::curvesSelected, m_view, &GraphicsView::reset);
|
|
||||||
|
|
||||||
connect(m_tree, &TreeView::treeItemLocked, model, &CurveEditorModel::curveChanged);
|
connect(m_tree, &TreeView::treeItemLocked, model, &CurveEditorModel::curveChanged);
|
||||||
connect(m_tree, &TreeView::treeItemPinned, model, &CurveEditorModel::curveChanged);
|
connect(m_tree, &TreeView::treeItemPinned, model, &CurveEditorModel::curveChanged);
|
||||||
|
|
||||||
|
connect(m_tree, &TreeView::treeItemLocked, m_view, &GraphicsView::setLocked);
|
||||||
|
connect(m_tree->selectionModel(),
|
||||||
|
&SelectionModel::curvesSelected,
|
||||||
|
m_view,
|
||||||
|
&GraphicsView::updateSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CurveEditor::zoomX(double zoom)
|
void CurveEditor::zoomX(double zoom)
|
||||||
@@ -74,7 +77,7 @@ void CurveEditor::clearCanvas()
|
|||||||
m_view->reset({});
|
m_view->reset({});
|
||||||
}
|
}
|
||||||
|
|
||||||
QToolBar *CurveEditor::createToolBar()
|
QToolBar *CurveEditor::createToolBar(CurveEditorModel *model)
|
||||||
{
|
{
|
||||||
auto *bar = new QToolBar;
|
auto *bar = new QToolBar;
|
||||||
bar->setFloatable(false);
|
bar->setFloatable(false);
|
||||||
@@ -111,8 +114,38 @@ QToolBar *CurveEditor::createToolBar()
|
|||||||
bar->addWidget(valueWidget);
|
bar->addWidget(valueWidget);
|
||||||
|
|
||||||
auto *durationBox = new QHBoxLayout;
|
auto *durationBox = new QHBoxLayout;
|
||||||
durationBox->addWidget(new QLabel(tr("Duration")));
|
auto *startSpin = new QSpinBox;
|
||||||
durationBox->addWidget(new QSpinBox);
|
auto *endSpin = new QSpinBox;
|
||||||
|
|
||||||
|
startSpin->setRange(std::numeric_limits<int>::lowest(), std::numeric_limits<int>::max());
|
||||||
|
startSpin->setValue(model->minimumTime());
|
||||||
|
|
||||||
|
auto updateStartFrame = [this, model](int frame) {
|
||||||
|
model->setMinimumTime(frame, false);
|
||||||
|
m_view->viewport()->update();
|
||||||
|
};
|
||||||
|
connect(startSpin, QOverload<int>::of(&QSpinBox::valueChanged), updateStartFrame);
|
||||||
|
|
||||||
|
endSpin->setRange(std::numeric_limits<int>::lowest(), std::numeric_limits<int>::max());
|
||||||
|
endSpin->setValue(model->maximumTime());
|
||||||
|
|
||||||
|
auto updateEndFrame = [this, model](int frame) {
|
||||||
|
model->setMaximumTime(frame, false);
|
||||||
|
m_view->viewport()->update();
|
||||||
|
};
|
||||||
|
connect(endSpin, QOverload<int>::of(&QSpinBox::valueChanged), updateEndFrame);
|
||||||
|
|
||||||
|
auto setStartSlot = [startSpin](int frame) { startSpin->setValue(frame); };
|
||||||
|
connect(model, &CurveEditorModel::updateStartFrame, setStartSlot);
|
||||||
|
|
||||||
|
auto setEndSlot = [endSpin](int frame) { endSpin->setValue(frame); };
|
||||||
|
connect(model, &CurveEditorModel::updateEndFrame, setEndSlot);
|
||||||
|
|
||||||
|
durationBox->addWidget(new QLabel(tr("Start Frame")));
|
||||||
|
durationBox->addWidget(startSpin);
|
||||||
|
durationBox->addWidget(new QLabel(tr("End Frame")));
|
||||||
|
durationBox->addWidget(endSpin);
|
||||||
|
|
||||||
auto *durationWidget = new QWidget;
|
auto *durationWidget = new QWidget;
|
||||||
durationWidget->setLayout(durationBox);
|
durationWidget->setLayout(durationBox);
|
||||||
bar->addWidget(durationWidget);
|
bar->addWidget(durationWidget);
|
||||||
|
@@ -48,7 +48,7 @@ public:
|
|||||||
void clearCanvas();
|
void clearCanvas();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QToolBar *createToolBar();
|
QToolBar *createToolBar(CurveEditorModel *model);
|
||||||
|
|
||||||
TreeView *m_tree;
|
TreeView *m_tree;
|
||||||
|
|
||||||
|
@@ -25,13 +25,16 @@
|
|||||||
|
|
||||||
#include "curveeditormodel.h"
|
#include "curveeditormodel.h"
|
||||||
#include "treeitem.h"
|
#include "treeitem.h"
|
||||||
|
|
||||||
#include "detail/graphicsview.h"
|
#include "detail/graphicsview.h"
|
||||||
#include "detail/selectionmodel.h"
|
#include "detail/selectionmodel.h"
|
||||||
|
|
||||||
namespace DesignTools {
|
namespace DesignTools {
|
||||||
|
|
||||||
CurveEditorModel::CurveEditorModel(QObject *parent)
|
CurveEditorModel::CurveEditorModel(double minTime, double maxTime, QObject *parent)
|
||||||
: TreeModel(parent)
|
: TreeModel(parent)
|
||||||
|
, m_minTime(minTime)
|
||||||
|
, m_maxTime(maxTime)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
CurveEditorModel::~CurveEditorModel() {}
|
CurveEditorModel::~CurveEditorModel() {}
|
||||||
@@ -42,6 +45,24 @@ void CurveEditorModel::setCurrentFrame(int frame)
|
|||||||
graphicsView()->setCurrentFrame(frame);
|
graphicsView()->setCurrentFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CurveEditorModel::setMinimumTime(double time, bool internal)
|
||||||
|
{
|
||||||
|
m_minTime = time;
|
||||||
|
if (internal)
|
||||||
|
emit updateStartFrame(m_minTime);
|
||||||
|
else
|
||||||
|
emit startFrameChanged(m_minTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurveEditorModel::setMaximumTime(double time, bool internal)
|
||||||
|
{
|
||||||
|
m_maxTime = time;
|
||||||
|
if (internal)
|
||||||
|
emit updateEndFrame(m_maxTime);
|
||||||
|
else
|
||||||
|
emit endFrameChanged(m_maxTime);
|
||||||
|
}
|
||||||
|
|
||||||
void CurveEditorModel::setCurve(unsigned int id, const AnimationCurve &curve)
|
void CurveEditorModel::setCurve(unsigned int id, const AnimationCurve &curve)
|
||||||
{
|
{
|
||||||
if (TreeItem *item = find(id)) {
|
if (TreeItem *item = find(id)) {
|
||||||
|
@@ -48,6 +48,14 @@ class CurveEditorModel : public TreeModel
|
|||||||
signals:
|
signals:
|
||||||
void currentFrameChanged(int frame);
|
void currentFrameChanged(int frame);
|
||||||
|
|
||||||
|
void startFrameChanged(int frame);
|
||||||
|
|
||||||
|
void endFrameChanged(int frame);
|
||||||
|
|
||||||
|
void updateStartFrame(int frame);
|
||||||
|
|
||||||
|
void updateEndFrame(int frame);
|
||||||
|
|
||||||
void curveChanged(PropertyTreeItem *item);
|
void curveChanged(PropertyTreeItem *item);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -58,15 +66,24 @@ public:
|
|||||||
virtual CurveEditorStyle style() const = 0;
|
virtual CurveEditorStyle style() const = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CurveEditorModel(QObject *parent = nullptr);
|
CurveEditorModel(double minTime, double maxTime, QObject *parent = nullptr);
|
||||||
|
|
||||||
~CurveEditorModel() override;
|
~CurveEditorModel() override;
|
||||||
|
|
||||||
void setCurrentFrame(int frame);
|
void setCurrentFrame(int frame);
|
||||||
|
|
||||||
|
void setMinimumTime(double time, bool internal);
|
||||||
|
|
||||||
|
void setMaximumTime(double time, bool internal);
|
||||||
|
|
||||||
void setCurve(unsigned int id, const AnimationCurve &curve);
|
void setCurve(unsigned int id, const AnimationCurve &curve);
|
||||||
|
|
||||||
void reset(const std::vector<TreeItem *> &items);
|
void reset(const std::vector<TreeItem *> &items);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
double m_minTime = 0.;
|
||||||
|
|
||||||
|
double m_maxTime = 0.;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // End namespace DesignTools.
|
} // End namespace DesignTools.
|
||||||
|
@@ -230,6 +230,16 @@ void GraphicsView::scrollContent(double x, double y)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::reset(const std::vector<CurveItem *> &items)
|
void GraphicsView::reset(const std::vector<CurveItem *> &items)
|
||||||
|
{
|
||||||
|
m_scene.clear();
|
||||||
|
for (auto *item : items)
|
||||||
|
m_scene.addCurveItem(item);
|
||||||
|
|
||||||
|
applyZoom(m_zoomX, m_zoomY);
|
||||||
|
viewport()->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsView::updateSelection(const std::vector<CurveItem *> &items)
|
||||||
{
|
{
|
||||||
const std::vector<CurveItem *> pinnedItems = m_scene.takePinnedItems();
|
const std::vector<CurveItem *> pinnedItems = m_scene.takePinnedItems();
|
||||||
auto notPinned = [pinnedItems](CurveItem *item) {
|
auto notPinned = [pinnedItems](CurveItem *item) {
|
||||||
@@ -541,6 +551,38 @@ void GraphicsView::drawExtremaY(QPainter *painter, const QRectF &rect)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void GraphicsView::drawRangeBar(QPainter *painter, const QRectF &rect)
|
||||||
|
{
|
||||||
|
QFontMetrics fm(painter->font());
|
||||||
|
QRectF labelRect = fm.boundingRect(QString("0"));
|
||||||
|
labelRect.moveCenter(rect.center());
|
||||||
|
|
||||||
|
qreal bTick = rect.bottom() - 2;
|
||||||
|
qreal tTick = labelRect.bottom() + 2;
|
||||||
|
QRectF activeRect = QRectF(QPointF(mapTimeToX(m_model->minimumTime()), tTick),
|
||||||
|
QPointF(mapTimeToX(m_model->maximumTime()), bTick));
|
||||||
|
|
||||||
|
QColor color = Qt::white;
|
||||||
|
color.setAlpha(30);
|
||||||
|
|
||||||
|
painter->fillRect(activeRect, color);
|
||||||
|
|
||||||
|
QColor handleColor(Qt::green);
|
||||||
|
painter->setBrush(handleColor);
|
||||||
|
painter->setPen(handleColor);
|
||||||
|
|
||||||
|
const qreal radius = 5.;
|
||||||
|
QRectF minHandle = rangeMinHandle(rect);
|
||||||
|
painter->drawRoundedRect(minHandle, radius, radius);
|
||||||
|
minHandle.setLeft(minHandle.center().x());
|
||||||
|
painter->fillRect(minHandle, Qt::green);
|
||||||
|
|
||||||
|
QRectF maxHandle = rangeMaxHandle(rect);
|
||||||
|
painter->drawRoundedRect(maxHandle, radius, radius);
|
||||||
|
maxHandle.setRight(maxHandle.center().x());
|
||||||
|
painter->fillRect(maxHandle, Qt::green);
|
||||||
|
}
|
||||||
|
|
||||||
void GraphicsView::drawTimeScale(QPainter *painter, const QRectF &rect)
|
void GraphicsView::drawTimeScale(QPainter *painter, const QRectF &rect)
|
||||||
{
|
{
|
||||||
painter->save();
|
painter->save();
|
||||||
@@ -565,6 +607,8 @@ void GraphicsView::drawTimeScale(QPainter *painter, const QRectF &rect)
|
|||||||
for (double i = minimumTime(); i <= maximumTime(); i += timeIncrement)
|
for (double i = minimumTime(); i <= maximumTime(); i += timeIncrement)
|
||||||
paintLabeledTick(i);
|
paintLabeledTick(i);
|
||||||
|
|
||||||
|
drawRangeBar(painter, rect);
|
||||||
|
|
||||||
painter->restore();
|
painter->restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,4 +663,28 @@ double GraphicsView::timeLabelInterval(QPainter *painter, double maxTime)
|
|||||||
return deltaTime;
|
return deltaTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QRectF GraphicsView::rangeMinHandle(const QRectF &rect)
|
||||||
|
{
|
||||||
|
QRectF labelRect = fontMetrics().boundingRect(QString("0"));
|
||||||
|
labelRect.moveCenter(rect.center());
|
||||||
|
|
||||||
|
qreal top = rect.bottom() - 2;
|
||||||
|
qreal bottom = labelRect.bottom() + 2;
|
||||||
|
QSize size(10, top - bottom);
|
||||||
|
|
||||||
|
int leftHandleLeft = mapTimeToX(m_model->minimumTime()) - size.width();
|
||||||
|
return QRectF(QPointF(leftHandleLeft, bottom), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
QRectF GraphicsView::rangeMaxHandle(const QRectF &rect)
|
||||||
|
{
|
||||||
|
QRectF labelRect = fontMetrics().boundingRect(QString("0"));
|
||||||
|
labelRect.moveCenter(rect.center());
|
||||||
|
|
||||||
|
qreal bottom = rect.bottom() - 2;
|
||||||
|
qreal top = labelRect.bottom() + 2;
|
||||||
|
|
||||||
|
return QRectF(QPointF(mapTimeToX(m_model->maximumTime()), bottom), QSize(10, top - bottom));
|
||||||
|
}
|
||||||
|
|
||||||
} // End namespace DesignTools.
|
} // End namespace DesignTools.
|
||||||
|
@@ -108,6 +108,8 @@ public:
|
|||||||
|
|
||||||
void reset(const std::vector<CurveItem *> &items);
|
void reset(const std::vector<CurveItem *> &items);
|
||||||
|
|
||||||
|
void updateSelection(const std::vector<CurveItem *> &items);
|
||||||
|
|
||||||
void setInterpolation(Keyframe::Interpolation interpol);
|
void setInterpolation(Keyframe::Interpolation interpol);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -148,8 +150,14 @@ private:
|
|||||||
|
|
||||||
void drawValueScale(QPainter *painter, const QRectF &rect);
|
void drawValueScale(QPainter *painter, const QRectF &rect);
|
||||||
|
|
||||||
|
void drawRangeBar(QPainter *painter, const QRectF &rect);
|
||||||
|
|
||||||
double timeLabelInterval(QPainter *painter, double maxTime);
|
double timeLabelInterval(QPainter *painter, double maxTime);
|
||||||
|
|
||||||
|
QRectF rangeMinHandle(const QRectF &rect);
|
||||||
|
|
||||||
|
QRectF rangeMaxHandle(const QRectF &rect);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
double m_zoomX;
|
double m_zoomX;
|
||||||
|
|
||||||
|
@@ -35,9 +35,7 @@
|
|||||||
namespace QmlDesigner {
|
namespace QmlDesigner {
|
||||||
|
|
||||||
AnimationCurveEditorModel::AnimationCurveEditorModel(double minTime, double maxTime)
|
AnimationCurveEditorModel::AnimationCurveEditorModel(double minTime, double maxTime)
|
||||||
: CurveEditorModel()
|
: CurveEditorModel(minTime, maxTime)
|
||||||
, m_minTime(minTime)
|
|
||||||
, m_maxTime(maxTime)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
AnimationCurveEditorModel::~AnimationCurveEditorModel() {}
|
AnimationCurveEditorModel::~AnimationCurveEditorModel() {}
|
||||||
@@ -102,16 +100,6 @@ void AnimationCurveEditorModel::setTimeline(const QmlTimeline &timeline)
|
|||||||
reset(items);
|
reset(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationCurveEditorModel::setMinimumTime(double time)
|
|
||||||
{
|
|
||||||
m_minTime = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnimationCurveEditorModel::setMaximumTime(double time)
|
|
||||||
{
|
|
||||||
m_maxTime = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
DesignTools::ValueType typeFrom(const QmlTimelineKeyframeGroup &group)
|
DesignTools::ValueType typeFrom(const QmlTimelineKeyframeGroup &group)
|
||||||
{
|
{
|
||||||
if (group.valueType() == TypeName("double") || group.valueType() == TypeName("real")
|
if (group.valueType() == TypeName("double") || group.valueType() == TypeName("real")
|
||||||
|
@@ -49,20 +49,12 @@ public:
|
|||||||
|
|
||||||
void setTimeline(const QmlTimeline &timeline);
|
void setTimeline(const QmlTimeline &timeline);
|
||||||
|
|
||||||
void setMinimumTime(double time);
|
|
||||||
|
|
||||||
void setMaximumTime(double time);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DesignTools::TreeItem *createTopLevelItem(const QmlTimeline &timeline, const ModelNode &node);
|
DesignTools::TreeItem *createTopLevelItem(const QmlTimeline &timeline, const ModelNode &node);
|
||||||
|
|
||||||
DesignTools::AnimationCurve createAnimationCurve(const QmlTimelineKeyframeGroup &group);
|
DesignTools::AnimationCurve createAnimationCurve(const QmlTimelineKeyframeGroup &group);
|
||||||
|
|
||||||
DesignTools::AnimationCurve createDoubleCurve(const QmlTimelineKeyframeGroup &group);
|
DesignTools::AnimationCurve createDoubleCurve(const QmlTimelineKeyframeGroup &group);
|
||||||
|
|
||||||
double m_minTime;
|
|
||||||
|
|
||||||
double m_maxTime;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
} // namespace QmlDesigner
|
||||||
|
@@ -38,7 +38,6 @@
|
|||||||
#include <variantproperty.h>
|
#include <variantproperty.h>
|
||||||
#include <qmlstate.h>
|
#include <qmlstate.h>
|
||||||
#include <qmltimeline.h>
|
#include <qmltimeline.h>
|
||||||
#include <qmltimelinekeyframegroup.h>
|
|
||||||
|
|
||||||
#include <coreplugin/actionmanager/actionmanager.h>
|
#include <coreplugin/actionmanager/actionmanager.h>
|
||||||
#include <coreplugin/actionmanager/command.h>
|
#include <coreplugin/actionmanager/command.h>
|
||||||
@@ -112,6 +111,23 @@ TimelineToolBar::TimelineToolBar(QWidget *parent)
|
|||||||
&AnimationCurveEditorModel::currentFrameChanged,
|
&AnimationCurveEditorModel::currentFrameChanged,
|
||||||
this,
|
this,
|
||||||
&TimelineToolBar::currentFrameChanged);
|
&TimelineToolBar::currentFrameChanged);
|
||||||
|
|
||||||
|
auto setStartFrameValue = [this](int val) {
|
||||||
|
if (m_firstFrame) {
|
||||||
|
m_firstFrame->setText(QString::number(val, 'f', 0));
|
||||||
|
emit startFrameChanged(val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connect(m_curveModel, &AnimationCurveEditorModel::startFrameChanged, setStartFrameValue);
|
||||||
|
|
||||||
|
auto setEndFrameValue = [this](int val) {
|
||||||
|
if (m_lastFrame) {
|
||||||
|
m_lastFrame->setText(QString::number(val, 'f', 0));
|
||||||
|
emit endFrameChanged(val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connect(m_curveModel, &AnimationCurveEditorModel::endFrameChanged, setEndFrameValue);
|
||||||
|
|
||||||
connect(m_curveModel,
|
connect(m_curveModel,
|
||||||
&AnimationCurveEditorModel::curveChanged,
|
&AnimationCurveEditorModel::curveChanged,
|
||||||
this,
|
this,
|
||||||
@@ -181,7 +197,7 @@ void TimelineToolBar::setCurrentTimeline(const QmlTimeline &timeline)
|
|||||||
|
|
||||||
void TimelineToolBar::setStartFrame(qreal frame)
|
void TimelineToolBar::setStartFrame(qreal frame)
|
||||||
{
|
{
|
||||||
m_curveModel->setMinimumTime(frame);
|
m_curveModel->setMinimumTime(frame, true);
|
||||||
|
|
||||||
auto text = QString::number(frame, 'f', 0);
|
auto text = QString::number(frame, 'f', 0);
|
||||||
m_firstFrame->setText(text);
|
m_firstFrame->setText(text);
|
||||||
@@ -198,7 +214,7 @@ void TimelineToolBar::setCurrentFrame(qreal frame)
|
|||||||
|
|
||||||
void TimelineToolBar::setEndFrame(qreal frame)
|
void TimelineToolBar::setEndFrame(qreal frame)
|
||||||
{
|
{
|
||||||
m_curveModel->setMaximumTime(frame);
|
m_curveModel->setMaximumTime(frame, true);
|
||||||
|
|
||||||
auto text = QString::number(frame, 'f', 0);
|
auto text = QString::number(frame, 'f', 0);
|
||||||
m_lastFrame->setText(text);
|
m_lastFrame->setText(text);
|
||||||
|
@@ -459,6 +459,8 @@ void TimelineWidget::invalidateTimelineDuration(const QmlTimeline &timeline)
|
|||||||
if (timelineView() && timelineView()->model()) {
|
if (timelineView() && timelineView()->model()) {
|
||||||
QmlTimeline currentTimeline = graphicsScene()->currentTimeline();
|
QmlTimeline currentTimeline = graphicsScene()->currentTimeline();
|
||||||
if (currentTimeline.isValid() && currentTimeline == timeline) {
|
if (currentTimeline.isValid() && currentTimeline == timeline) {
|
||||||
|
m_toolbar->setStartFrame(timeline.startKeyframe());
|
||||||
|
m_toolbar->setEndFrame(timeline.endKeyframe());
|
||||||
graphicsScene()->setTimeline(timeline);
|
graphicsScene()->setTimeline(timeline);
|
||||||
|
|
||||||
qreal playHeadFrame = getcurrentFrame(timeline);
|
qreal playHeadFrame = getcurrentFrame(timeline);
|
||||||
|
Reference in New Issue
Block a user