transition code rewrite for #110

This commit is contained in:
itsmattkc
2018-11-20 22:02:41 +11:00
parent bf0874dbe5
commit e785586df2
35 changed files with 1021 additions and 1378 deletions
+10 -556
View File
@@ -7,19 +7,15 @@
#include "panels/project.h"
#include "project/undo.h"
#include "project/sequence.h"
#include "ui/labelslider.h"
#include "ui/colorbutton.h"
#include "ui/comboboxex.h"
#include "ui/fontcombobox.h"
#include "ui/checkboxex.h"
#include "ui/texteditex.h"
#include "project/clip.h"
#include "panels/timeline.h"
#include "panels/effectcontrols.h"
#include "ui/checkboxex.h"
#include "debug.h"
#include "io/path.h"
#include "mainwindow.h"
#include "io/math.h"
#include "transition.h"
#include "effects/internal/transformeffect.h"
#include "effects/internal/texteffect.h"
@@ -29,10 +25,6 @@
#include "effects/internal/volumeeffect.h"
#include "effects/internal/paneffect.h"
#include "effects/internal/shakeeffect.h"
#include "effects/internal/crossdissolvetransition.h"
#include "effects/internal/linearfadetransition.h"
#include "effects/internal/exponentialfadetransition.h"
#include "effects/internal/logarithmicfadetransition.h"
#include "effects/internal/cornerpineffect.h"
#include <QCheckBox>
@@ -64,11 +56,7 @@ Effect* create_effect(Clip* c, const EffectMeta* em) {
case EFFECT_INTERNAL_VOLUME: return new VolumeEffect(c, em);
case EFFECT_INTERNAL_PAN: return new PanEffect(c, em);
case EFFECT_INTERNAL_TONE: return new ToneEffect(c, em);
case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em);
case EFFECT_INTERNAL_CROSSDISSOLVE: return new CrossDissolveTransition(c, em);
case EFFECT_INTERNAL_LINEARFADE: return new LinearFadeTransition(c, em);
case EFFECT_INTERNAL_EXPONENTIALFADE: return new ExponentialFadeTransition(c, em);
case EFFECT_INTERNAL_LOGARITHMICFADE: return new LogarithmicFadeTransition(c, em);
case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em);
case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em);
}
} else {
@@ -144,19 +132,19 @@ void load_internal_effects() {
em.category = "";
em.name = "Cross Dissolve";
em.internal = EFFECT_INTERNAL_CROSSDISSOLVE;
em.internal = TRANSITION_INTERNAL_CROSSDISSOLVE;
video_effects.append(em);
em.name = "Linear Fade";
em.internal = EFFECT_INTERNAL_LINEARFADE;
em.internal = TRANSITION_INTERNAL_LINEARFADE;
audio_effects.append(em);
em.name = "Exponential Fade";
em.internal = EFFECT_INTERNAL_EXPONENTIALFADE;
em.internal = TRANSITION_INTERNAL_EXPONENTIALFADE;
audio_effects.append(em);
em.name = "Logarithmic Fade";
em.internal = EFFECT_INTERNAL_LOGARITHMICFADE;
em.internal = TRANSITION_INTERNAL_LOGARITHMICFADE;
audio_effects.append(em);
}
@@ -233,7 +221,6 @@ Effect::Effect(Clip* c, const EffectMeta *em) :
parent_clip(c),
meta(em),
length(30),
tlink(NULL),
enable_shader(false),
enable_coords(false),
enable_superimpose(false),
@@ -257,10 +244,6 @@ Effect::Effect(Clip* c, const EffectMeta *em) :
// set up UI from effect file
container->setText(em->name);
if (em->type == EFFECT_TYPE_TRANSITION) {
add_row("Length:")->add_field(EFFECT_FIELD_DOUBLE);
}
if (!em->filename.isEmpty()) {
QFile effect_file(get_effects_dir() + "/" + em->filename);
if (effect_file.open(QFile::ReadOnly)) {
@@ -682,8 +665,8 @@ void Effect::load(QXmlStreamReader& stream) {
void Effect::save(QXmlStreamWriter& stream) {
stream.writeAttribute("name", meta->name);
stream.writeAttribute("enabled", QString::number(is_enabled()));
stream.writeAttribute("length", QString::number(length));
stream.writeAttribute("enabled", QString::number(is_enabled()));
stream.writeAttribute("length", QString::number(length));
for (int i=0;i<rows.size();i++) {
EffectRow* row = rows.at(i);
@@ -762,8 +745,7 @@ void Effect::endEffect() {
Effect* Effect::copy(Clip* c) {
Effect* copy = create_effect(c, meta);
copy->set_enabled(is_enabled());
copy->length = length;
copy->set_enabled(is_enabled());
copy_field_keyframes(copy);
return copy;
}
@@ -929,534 +911,6 @@ void Effect::delete_texture() {
}
}
/* Effect Row Definitions */
EffectRow::EffectRow(Effect *parent, QGridLayout *uilayout, const QString &n, int row) :
parent_effect(parent),
keyframing(false),
ui(uilayout),
name(n),
ui_row(row),
just_made_unsafe_keyframe(false)
{
label = new QLabel(name);
ui->addWidget(label, row, 0);
QSize button_size(20, 20);
QSize icon_size(12, 12);
QHBoxLayout* key_controls = new QHBoxLayout();
key_controls->setSpacing(0);
key_controls->setMargin(0);
key_controls->addStretch();
left_key_nav = new QPushButton("<");
left_key_nav->setVisible(false);
left_key_nav->setMaximumSize(button_size);
key_controls->addWidget(left_key_nav);
connect(left_key_nav, SIGNAL(clicked(bool)), this, SLOT(goto_previous_key()));
key_addremove = new QPushButton(".");
key_addremove->setVisible(false);
key_addremove->setMaximumSize(button_size);
key_controls->addWidget(key_addremove);
connect(key_addremove, SIGNAL(clicked(bool)), this, SLOT(toggle_key()));
right_key_nav = new QPushButton(">");
right_key_nav->setVisible(false);
right_key_nav->setMaximumSize(button_size);
key_controls->addWidget(right_key_nav);
connect(right_key_nav, SIGNAL(clicked(bool)), this, SLOT(goto_next_key()));
keyframe_enable = new QPushButton(QIcon(":/icons/clock.png"), "");
keyframe_enable->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
keyframe_enable->setMaximumSize(button_size);
keyframe_enable->setIconSize(icon_size);
keyframe_enable->setCheckable(true);
keyframe_enable->setToolTip("Enable Keyframes");
connect(keyframe_enable, SIGNAL(clicked(bool)), this, SLOT(set_keyframe_enabled(bool)));
connect(keyframe_enable, SIGNAL(toggled(bool)), this, SLOT(keyframe_ui_enabled(bool)));
key_controls->addWidget(keyframe_enable);
ui->addLayout(key_controls, row, 6);
}
bool EffectRow::isKeyframing() {
return keyframing;
}
void EffectRow::setKeyframing(bool b) {
keyframing = b;
keyframe_enable->setChecked(b);
}
void EffectRow::set_keyframe_enabled(bool enabled) {
if (enabled) {
set_keyframe_now(true);
} else {
if (QMessageBox::question(panel_effect_controls, "Disable Keyframes", "Disabling keyframes will delete all current keyframes. Are you sure you want to do this?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
// clear
KeyframeDelete* kd = new KeyframeDelete();
for (int i=keyframe_times.size()-1;i>=0;i--) {
delete_keyframe(kd, i);
}
kd->disable_keyframes_on_row = this;
undo_stack.push(kd);
panel_effect_controls->update_keyframes();
} else {
setKeyframing(true);
}
}
}
void EffectRow::keyframe_ui_enabled(bool enabled) {
left_key_nav->setVisible(enabled);
key_addremove->setVisible(enabled);
right_key_nav->setVisible(enabled);
}
void EffectRow::goto_previous_key() {
long key = LONG_MIN;
Clip* c = parent_effect->parent_clip;
for (int i=0;i<keyframe_times.size();i++) {
long comp = keyframe_times.at(i) - c->clip_in + c->timeline_in;
if (comp < sequence->playhead) {
key = qMax(comp, key);
}
}
if (key != LONG_MIN) panel_sequence_viewer->seek(key);
}
void EffectRow::toggle_key() {
int index = -1;
Clip* c = parent_effect->parent_clip;
for (int i=0;i<keyframe_times.size();i++) {
long comp = c->timeline_in - c->clip_in + keyframe_times.at(i);
if (comp == sequence->playhead) {
index = i;
break;
}
}
if (index < 0) {
// keyframe doesn't exist, set one
set_keyframe_now(true);
} else {
KeyframeDelete* kd = new KeyframeDelete();
delete_keyframe(kd, index);
undo_stack.push(kd);
panel_effect_controls->update_keyframes();
panel_sequence_viewer->viewer_widget->update();
}
}
void EffectRow::goto_next_key() {
long key = LONG_MAX;
Clip* c = parent_effect->parent_clip;
for (int i=0;i<keyframe_times.size();i++) {
long comp = c->timeline_in - c->clip_in + keyframe_times.at(i);
if (comp > sequence->playhead) {
key = qMin(comp, key);
}
}
if (key != LONG_MAX) panel_sequence_viewer->seek(key);
}
EffectField* EffectRow::add_field(int type, int colspan) {
EffectField* field = new EffectField(this, type);
fields.append(field);
QWidget* element = field->get_ui_element();
ui->addWidget(element, ui_row, fields.size(), 1, colspan);
return field;
}
EffectRow::~EffectRow() {
for (int i=0;i<fields.size();i++) {
delete fields.at(i);
}
}
void EffectRow::set_keyframe_now(bool undoable) {
int index = -1;
long time = sequence->playhead-parent_effect->parent_clip->timeline_in+parent_effect->parent_clip->clip_in;
for (int i=0;i<keyframe_times.size();i++) {
if (keyframe_times.at(i) == time) {
index = i;
break;
}
}
KeyframeSet* ks = new KeyframeSet(this, index, time, just_made_unsafe_keyframe);
if (undoable) {
just_made_unsafe_keyframe = false;
undo_stack.push(ks);
} else {
if (index == -1) just_made_unsafe_keyframe = true;
ks->redo();
delete ks;
}
panel_effect_controls->update_keyframes();
}
void EffectRow::delete_keyframe_at_time(KeyframeDelete* kd, long time) {
for (int i=0;i<keyframe_times.size();i++) {
if (keyframe_times.at(i) == time) {
delete_keyframe(kd, i);
break;
}
}
}
void EffectRow::delete_keyframe(KeyframeDelete* kd, int index) {
kd->rows.append(this);
kd->keyframes.append(index);
}
EffectField* EffectRow::field(int i) {
return fields.at(i);
}
int EffectRow::fieldCount() {
return fields.size();
}
/* Effect Field Definitions */
EffectField::EffectField(EffectRow *parent, int t) : parent_row(parent), type(t) {
switch (t) {
case EFFECT_FIELD_DOUBLE:
{
LabelSlider* ls = new LabelSlider();
ui_element = ls;
connect(ls, SIGNAL(valueChanged()), this, SLOT(uiElementChange()));
}
break;
case EFFECT_FIELD_COLOR:
{
ColorButton* cb = new ColorButton();
ui_element = cb;
connect(cb, SIGNAL(color_changed()), this, SLOT(uiElementChange()));
}
break;
case EFFECT_FIELD_STRING:
{
TextEditEx* edit = new TextEditEx();
edit->setUndoRedoEnabled(true);
ui_element = edit;
connect(edit, SIGNAL(textChanged()), this, SLOT(uiElementChange()));
}
break;
case EFFECT_FIELD_BOOL:
{
CheckboxEx* cb = new CheckboxEx();
ui_element = cb;
connect(cb, SIGNAL(clicked(bool)), this, SLOT(uiElementChange()));
connect(cb, SIGNAL(toggled(bool)), this, SIGNAL(toggled(bool)));
}
break;
case EFFECT_FIELD_COMBO:
{
ComboBoxEx* cb = new ComboBoxEx();
ui_element = cb;
connect(cb, SIGNAL(activated(int)), this, SLOT(uiElementChange()));
}
break;
case EFFECT_FIELD_FONT:
{
FontCombobox* fcb = new FontCombobox();
ui_element = fcb;
connect(fcb, SIGNAL(activated(int)), this, SLOT(uiElementChange()));
}
break;
}
}
QVariant EffectField::get_previous_data() {
switch (type) {
case EFFECT_FIELD_DOUBLE: return static_cast<LabelSlider*>(ui_element)->getPreviousValue(); break;
case EFFECT_FIELD_COLOR: return static_cast<ColorButton*>(ui_element)->getPreviousValue(); break;
case EFFECT_FIELD_STRING: return static_cast<TextEditEx*>(ui_element)->getPreviousValue(); break;
case EFFECT_FIELD_BOOL: return !static_cast<QCheckBox*>(ui_element)->isChecked(); break;
case EFFECT_FIELD_COMBO: return static_cast<ComboBoxEx*>(ui_element)->getPreviousIndex(); break;
case EFFECT_FIELD_FONT: return static_cast<FontCombobox*>(ui_element)->getPreviousValue(); break;
}
return QVariant();
}
QVariant EffectField::get_current_data() {
switch (type) {
case EFFECT_FIELD_DOUBLE: return static_cast<LabelSlider*>(ui_element)->value(); break;
case EFFECT_FIELD_COLOR: return static_cast<ColorButton*>(ui_element)->get_color(); break;
case EFFECT_FIELD_STRING: return static_cast<TextEditEx*>(ui_element)->getPlainTextEx(); break;
case EFFECT_FIELD_BOOL: return static_cast<QCheckBox*>(ui_element)->isChecked(); break;
case EFFECT_FIELD_COMBO: return static_cast<ComboBoxEx*>(ui_element)->currentIndex(); break;
case EFFECT_FIELD_FONT: return static_cast<FontCombobox*>(ui_element)->currentText(); break;
}
return QVariant();
}
double EffectField::frameToTimecode(long frame) {
return ((double) frame / parent_row->parent_effect->parent_clip->sequence->frame_rate);
}
long EffectField::timecodeToFrame(double timecode) {
return qRound(timecode * parent_row->parent_effect->parent_clip->sequence->frame_rate);
}
void EffectField::set_current_data(const QVariant& data) {
switch (type) {
case EFFECT_FIELD_DOUBLE: return static_cast<LabelSlider*>(ui_element)->set_value(data.toDouble(), false); break;
case EFFECT_FIELD_COLOR: return static_cast<ColorButton*>(ui_element)->set_color(data.value<QColor>()); break;
case EFFECT_FIELD_STRING: return static_cast<TextEditEx*>(ui_element)->setPlainTextEx(data.toString()); break;
case EFFECT_FIELD_BOOL: return static_cast<QCheckBox*>(ui_element)->setChecked(data.toBool()); break;
case EFFECT_FIELD_COMBO: return static_cast<ComboBoxEx*>(ui_element)->setCurrentIndexEx(data.toInt()); break;
case EFFECT_FIELD_FONT: return static_cast<FontCombobox*>(ui_element)->setCurrentTextEx(data.toString()); break;
}
}
void EffectField::get_keyframe_data(double timecode, int &before, int &after, double &progress) {
int before_keyframe_index = -1;
int after_keyframe_index = -1;
long before_keyframe_time = LONG_MIN;
long after_keyframe_time = LONG_MAX;
long frame = timecodeToFrame(timecode);
for (int i=0;i<parent_row->keyframe_times.size();i++) {
long eval_keyframe_time = parent_row->keyframe_times.at(i);
if (eval_keyframe_time == frame) {
before = i;
after = i;
return;
} else if (eval_keyframe_time < frame && eval_keyframe_time > before_keyframe_time) {
before_keyframe_index = i;
before_keyframe_time = eval_keyframe_time;
} else if (eval_keyframe_time > frame && eval_keyframe_time < after_keyframe_time) {
after_keyframe_index = i;
after_keyframe_time = eval_keyframe_time;
}
}
if ((type == EFFECT_FIELD_DOUBLE || type == EFFECT_FIELD_COLOR) && (before_keyframe_index > -1 && after_keyframe_index > -1)) {
// interpolate
before = before_keyframe_index;
after = after_keyframe_index;
progress = (timecode-frameToTimecode(before_keyframe_time))/(frameToTimecode(after_keyframe_time)-frameToTimecode(before_keyframe_time));
// TODO routines for bezier - currently this is purely linear
} else if (before_keyframe_index > -1) {
before = before_keyframe_index;
after = before_keyframe_index;
} else {
before = after_keyframe_index;
after = after_keyframe_index;
}
}
bool EffectField::hasKeyframes() {
return (parent_row->isKeyframing() && keyframe_data.size() > 0);
}
QVariant EffectField::validate_keyframe_data(double timecode, bool async) {
if (hasKeyframes()) {
int before_keyframe;
int after_keyframe;
double progress;
get_keyframe_data(timecode, before_keyframe, after_keyframe, progress);
int kf_type = (progress < 0.5) ? parent_row->keyframe_types.at(before_keyframe) : parent_row->keyframe_types.at(after_keyframe);
if (kf_type == KEYFRAME_TYPE_SMOOTH) {
double b = 4;
double c = 1.313;
double x = (b * progress) - (b*0.5);
progress = (c*(qPow(M_E, x)/(qPow(M_E, x) + 1)))-((c-1)*0.5);
}
const QVariant& before_data = keyframe_data.at(before_keyframe);
switch (type) {
case EFFECT_FIELD_DOUBLE:
{
double value;
if (before_keyframe == after_keyframe) {
value = keyframe_data.at(before_keyframe).toDouble();
} else {
double before_dbl = keyframe_data.at(before_keyframe).toDouble();
double after_dbl = keyframe_data.at(after_keyframe).toDouble();
value = double_lerp(before_dbl, after_dbl, progress);
}
if (async) {
return value;
}
static_cast<LabelSlider*>(ui_element)->set_value(value, false);
}
break;
case EFFECT_FIELD_COLOR:
{
QColor value;
if (before_keyframe == after_keyframe) {
value = keyframe_data.at(before_keyframe).value<QColor>();
} else {
QColor before_data = keyframe_data.at(before_keyframe).value<QColor>();
QColor after_data = keyframe_data.at(after_keyframe).value<QColor>();
value = QColor(lerp(before_data.red(), after_data.red(), progress), lerp(before_data.green(), after_data.green(), progress), lerp(before_data.blue(), after_data.blue(), progress));
}
if (async) {
return value;
}
static_cast<ColorButton*>(ui_element)->set_color(value);
}
break;
case EFFECT_FIELD_STRING:
if (async) {
return before_data;
}
static_cast<TextEditEx*>(ui_element)->setPlainTextEx(before_data.toString());
break;
case EFFECT_FIELD_BOOL:
if (async) {
return before_data;
}
static_cast<QCheckBox*>(ui_element)->setChecked(before_data.toBool());
break;
case EFFECT_FIELD_COMBO:
if (async) {
return before_data;
}
static_cast<ComboBoxEx*>(ui_element)->setCurrentIndexEx(before_data.toInt());
break;
case EFFECT_FIELD_FONT:
if (async) {
return before_data;
}
static_cast<FontCombobox*>(ui_element)->setCurrentTextEx(before_data.toString());
break;
}
}
return QVariant();
}
void EffectField::uiElementChange() {
bool enableKeyframes = !(type == EFFECT_FIELD_DOUBLE && static_cast<LabelSlider*>(ui_element)->is_dragging());
if (parent_row->isKeyframing()) {
parent_row->set_keyframe_now(enableKeyframes);
} else if (enableKeyframes) {
// set undo
undo_stack.push(new EffectFieldUndo(this));
}
emit changed();
}
QWidget* EffectField::get_ui_element() {
return ui_element;
}
void EffectField::set_enabled(bool e) {
ui_element->setEnabled(e);
}
double EffectField::get_double_value(double timecode, bool async) {
if (async && hasKeyframes()) {
return validate_keyframe_data(timecode, true).toDouble();
}
validate_keyframe_data(timecode);
return static_cast<LabelSlider*>(ui_element)->value();
}
void EffectField::set_double_value(double v) {
static_cast<LabelSlider*>(ui_element)->set_value(v, false);
}
void EffectField::set_double_default_value(double v) {
static_cast<LabelSlider*>(ui_element)->set_default_value(v);
}
void EffectField::set_double_minimum_value(double v) {
static_cast<LabelSlider*>(ui_element)->set_minimum_value(v);
}
void EffectField::set_double_maximum_value(double v) {
static_cast<LabelSlider*>(ui_element)->set_maximum_value(v);
}
void EffectField::add_combo_item(const QString& name, const QVariant& data) {
static_cast<ComboBoxEx*>(ui_element)->addItem(name, data);
}
int EffectField::get_combo_index(double timecode, bool async) {
if (async && hasKeyframes()) {
return validate_keyframe_data(timecode, true).toInt();
}
validate_keyframe_data(timecode);
return static_cast<ComboBoxEx*>(ui_element)->currentIndex();
}
const QVariant EffectField::get_combo_data(double timecode) {
validate_keyframe_data(timecode);
return static_cast<ComboBoxEx*>(ui_element)->currentData();
}
const QString EffectField::get_combo_string(double timecode) {
validate_keyframe_data(timecode);
return static_cast<ComboBoxEx*>(ui_element)->currentText();
}
void EffectField::set_combo_index(int index) {
static_cast<ComboBoxEx*>(ui_element)->setCurrentIndexEx(index);
}
void EffectField::set_combo_string(const QString& s) {
static_cast<ComboBoxEx*>(ui_element)->setCurrentTextEx(s);
}
bool EffectField::get_bool_value(double timecode, bool async) {
if (async && hasKeyframes()) {
return validate_keyframe_data(timecode, true).toBool();
}
validate_keyframe_data(timecode);
return static_cast<QCheckBox*>(ui_element)->isChecked();
}
void EffectField::set_bool_value(bool b) {
return static_cast<QCheckBox*>(ui_element)->setChecked(b);
}
const QString EffectField::get_string_value(double timecode, bool async) {
if (async && hasKeyframes()) {
return validate_keyframe_data(timecode, true).toString();
}
validate_keyframe_data(timecode);
return static_cast<TextEditEx*>(ui_element)->getPlainTextEx();
}
void EffectField::set_string_value(const QString& s) {
static_cast<TextEditEx*>(ui_element)->setPlainTextEx(s);
}
const QString EffectField::get_font_name(double timecode, bool async) {
if (async && hasKeyframes()) {
return validate_keyframe_data(timecode, true).toString();
}
validate_keyframe_data(timecode);
return static_cast<FontCombobox*>(ui_element)->currentText();
}
void EffectField::set_font_name(const QString& s) {
static_cast<FontCombobox*>(ui_element)->setCurrentText(s);
}
QColor EffectField::get_color_value(double timecode, bool async) {
if (async && hasKeyframes()) {
return validate_keyframe_data(timecode, true).value<QColor>();
}
validate_keyframe_data(timecode);
return static_cast<ColorButton*>(ui_element)->get_color();
}
void EffectField::set_color_value(QColor color) {
static_cast<ColorButton*>(ui_element)->set_color(color);
}
qint16 mix_audio_sample(qint16 a, qint16 b) {
qint32 mixed_sample = static_cast<qint32>(a) + static_cast<qint32>(b);
mixed_sample = qMax(qMin(mixed_sample, static_cast<qint32>(INT16_MAX)), static_cast<qint32>(INT16_MIN));