Update motion interpolation editor

master
Jeremy Hu 2019-11-06 20:20:00 +09:30
parent 4c6803ad8e
commit 6b3fe2b04b
4 changed files with 179 additions and 40 deletions

View File

@ -581,14 +581,6 @@ Tips:
</context>
<context>
<name>MotionTimelineWidget</name>
<message>
<source>Bouncing Begin:</source>
<translation></translation>
</message>
<message>
<source>Bouncing End:</source>
<translation></translation>
</message>
<message>
<source>Duration:</source>
<translation></translation>
@ -605,6 +597,26 @@ Tips:
<source>Delete</source>
<translation></translation>
</message>
<message>
<source>Linear</source>
<translation>线</translation>
</message>
<message>
<source>Cubic</source>
<translation>线</translation>
</message>
<message>
<source>Accelerating:</source>
<translation></translation>
</message>
<message>
<source>Bouncing:</source>
<translation></translation>
</message>
<message>
<source>Decelerating:</source>
<translation></translation>
</message>
</context>
<context>
<name>PartTreeWidget</name>

View File

@ -14,32 +14,65 @@ float calculateInterpolation(InterpolationType type, float knot)
return easing.valueForProgress(knot);
}
bool InterpolationIsLinear(InterpolationType type)
{
return QEasingCurve::Linear == InterpolationTypeToEasingCurveType(type);
}
bool InterpolationHasAccelerating(InterpolationType type)
{
QString name = InterpolationTypeToString(type);
if (-1 != name.indexOf("In"))
return true;
return false;
}
bool InterpolationHasDecelerating(InterpolationType type)
{
QString name = InterpolationTypeToString(type);
if (-1 != name.indexOf("Out"))
return true;
return false;
}
bool InterpolationIsBouncingBegin(InterpolationType type)
{
QString name = InterpolationTypeToString(type);
if (-1 == name.indexOf("InBack") && -1 == name.indexOf("InOutBack"))
return false;
if (-1 != name.indexOf("InBack") || -1 != name.indexOf("InOutBack"))
return true;
return false;
}
bool InterpolationIsBouncingEnd(InterpolationType type)
{
QString name = InterpolationTypeToString(type);
if (-1 == name.indexOf("OutBack") && -1 == name.indexOf("InOutBack"))
return false;
if (-1 != name.indexOf("OutBack") || -1 != name.indexOf("InOutBack"))
return true;
return false;
}
InterpolationType InterpolationMakeBouncingType(InterpolationType type, bool boucingBegin, bool bouncingEnd)
InterpolationType InterpolationMakeFromOptions(bool isLinear,
bool hasAccelerating, bool hasDecelerating,
bool boucingBegin, bool bouncingEnd)
{
Q_UNUSED(type);
if (boucingBegin && bouncingEnd)
if (isLinear)
return InterpolationType::Linear;
if (boucingBegin && bouncingEnd) {
return InterpolationType::EaseInOutBack;
else if (boucingBegin)
} else if (boucingBegin) {
return InterpolationType::EaseInBack;
else if (bouncingEnd)
} else if (bouncingEnd) {
return InterpolationType::EaseOutBack;
else
} else {
if (hasAccelerating && hasDecelerating) {
return InterpolationType::EaseInOutCubic;
} else if (hasAccelerating) {
return InterpolationType::EaseInCubic;
} else if (hasDecelerating) {
return InterpolationType::EaseOutCubic;
} else {
return InterpolationType::EaseInOutCubic;
}
}
}

View File

@ -215,9 +215,14 @@ QEasingCurve::Type InterpolationTypeToEasingCurveType(InterpolationType type)\
return s_types[index]; \
return QEasingCurve::Linear; \
}
bool InterpolationIsLinear(InterpolationType type);
bool InterpolationHasAccelerating(InterpolationType type);
bool InterpolationHasDecelerating(InterpolationType type);
bool InterpolationIsBouncingBegin(InterpolationType type);
bool InterpolationIsBouncingEnd(InterpolationType type);
InterpolationType InterpolationMakeBouncingType(InterpolationType type, bool boucingBegin, bool bouncingEnd);
InterpolationType InterpolationMakeFromOptions(bool isLinear,
bool hasAccelerating, bool hasDecelerating,
bool boucingBegin, bool bouncingEnd);
float calculateInterpolation(InterpolationType type, float knot);

View File

@ -5,11 +5,15 @@
#include <QCheckBox>
#include <QFormLayout>
#include <QDoubleSpinBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QStackedWidget>
#include "motiontimelinewidget.h"
#include "motionclipwidget.h"
#include "theme.h"
#include "posewidget.h"
#include "motionwidget.h"
#include "tabwidget.h"
MotionTimelineWidget::MotionTimelineWidget(const Document *document, QWidget *parent) :
QListWidget(parent),
@ -132,6 +136,9 @@ void MotionTimelineWidget::setClipInterpolationType(int index, InterpolationType
if (m_clips[index].clipType != MotionClipType::Interpolation)
return;
if (m_clips[index].interpolationType == type)
return;
m_clips[index].interpolationType = type;
emit clipsChanged();
}
@ -182,26 +189,72 @@ void MotionTimelineWidget::showInterpolationSettingPopup(int clipIndex, const QP
QWidget *popup = new QWidget;
QWidget *linearWidget = new QWidget;
QWidget *cubicWidget = new QWidget;
QCheckBox *hasAcceleratingBox = new QCheckBox();
QCheckBox *hasDeceleratingBox = new QCheckBox();
QCheckBox *bouncingBeginBox = new QCheckBox();
bouncingBeginBox->setChecked(InterpolationIsBouncingBegin(clips()[clipIndex].interpolationType));
connect(bouncingBeginBox, &QCheckBox::stateChanged, this, [=]() {
QCheckBox *bouncingEndBox = new QCheckBox();
QStackedWidget *stackedWidget = new QStackedWidget;
stackedWidget->addWidget(linearWidget);
stackedWidget->addWidget(cubicWidget);
bool currentIsLinear = InterpolationIsLinear(clips()[clipIndex].interpolationType);
std::vector<QString> tabs = {
tr("Linear"),
tr("Cubic")
};
TabWidget *tabWidget = new TabWidget(tabs);
tabWidget->setCurrentIndex(currentIsLinear ? 0 : 1);
stackedWidget->setCurrentIndex(currentIsLinear ? 0 : 1);
auto updateBoxes = [=](InterpolationType type) {
hasAcceleratingBox->setChecked(InterpolationHasAccelerating(type));
hasDeceleratingBox->setChecked(InterpolationHasDecelerating(type));
bouncingBeginBox->setChecked(InterpolationIsBouncingBegin(type));
bouncingEndBox->setChecked(InterpolationIsBouncingEnd(type));
};
updateBoxes(clips()[clipIndex].interpolationType);
auto updateInterpolation = [=]() {
bool isLinear = 0 == tabWidget->currentIndex();
bool bouncingBegin = bouncingBeginBox->isChecked();
const auto &oldType = clips()[clipIndex].interpolationType;
bool bouncingEnd = InterpolationIsBouncingEnd(oldType);
InterpolationType newType = InterpolationMakeBouncingType(oldType, bouncingBegin, bouncingEnd);
bool bouncingEnd = bouncingEndBox->isChecked();
bool hasAccelerating = bouncingBegin || hasAcceleratingBox->isChecked();
bool hasDecelerating = bouncingEnd || hasDeceleratingBox->isChecked();
InterpolationType newType = InterpolationMakeFromOptions(isLinear,
hasAccelerating, hasDecelerating,
bouncingBegin, bouncingEnd);
setClipInterpolationType(clipIndex, newType);
updateBoxes(newType);
};
connect(tabWidget, &TabWidget::currentIndexChanged, this, [=](int index) {
stackedWidget->setCurrentIndex(index);
updateInterpolation();
});
QCheckBox *bouncingEndBox = new QCheckBox();
bouncingEndBox->setChecked(InterpolationIsBouncingEnd(clips()[clipIndex].interpolationType));
connect(bouncingEndBox, &QCheckBox::stateChanged, this, [=]() {
bool bouncingEnd = bouncingEndBox->isChecked();
const auto &oldType = clips()[clipIndex].interpolationType;
bool bouncingBegin = InterpolationIsBouncingBegin(oldType);
InterpolationType newType = InterpolationMakeBouncingType(oldType, bouncingBegin, bouncingEnd);
setClipInterpolationType(clipIndex, newType);
connect(hasAcceleratingBox, &QCheckBox::stateChanged, this, [=]() {
updateInterpolation();
});
connect(hasDeceleratingBox, &QCheckBox::stateChanged, this, [=]() {
updateInterpolation();
});
connect(bouncingBeginBox, &QCheckBox::stateChanged, this, [=]() {
updateInterpolation();
});
connect(bouncingEndBox, &QCheckBox::stateChanged, this, [=]() {
updateInterpolation();
});
updateInterpolation();
QDoubleSpinBox *durationEdit = new QDoubleSpinBox();
durationEdit->setDecimals(2);
durationEdit->setMaximum(60);
@ -213,12 +266,48 @@ void MotionTimelineWidget::showInterpolationSettingPopup(int clipIndex, const QP
setClipDuration(clipIndex, (float)value);
});
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Bouncing Begin:"), bouncingBeginBox);
formLayout->addRow(tr("Bouncing End:"), bouncingEndBox);
formLayout->addRow(tr("Duration:"), durationEdit);
QVBoxLayout *mainLayout = new QVBoxLayout;
popup->setLayout(formLayout);
QVBoxLayout *cubicLayout = new QVBoxLayout;
QHBoxLayout *acceleratingLayout = new QHBoxLayout;
{
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Accelerating:"), hasAcceleratingBox);
acceleratingLayout->addLayout(formLayout);
}
{
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Bouncing:"), bouncingBeginBox);
acceleratingLayout->addLayout(formLayout);
}
cubicLayout->addLayout(acceleratingLayout);
QHBoxLayout *deceleratingLayout = new QHBoxLayout;
{
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Decelerating:"), hasDeceleratingBox);
deceleratingLayout->addLayout(formLayout);
}
{
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Bouncing:"), bouncingEndBox);
deceleratingLayout->addLayout(formLayout);
}
cubicLayout->addLayout(deceleratingLayout);
cubicWidget->setLayout(cubicLayout);
mainLayout->addWidget(tabWidget);
mainLayout->addWidget(stackedWidget);
{
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Duration:"), durationEdit);
mainLayout->addLayout(formLayout);
}
popup->setLayout(mainLayout);
QWidgetAction *action = new QWidgetAction(this);
action->setDefaultWidget(popup);