From 6ddabbaf3ac0fac3ca4d00cc26fb94b8d3f1c3fc Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 30 Dec 2019 16:02:51 +1100 Subject: [PATCH] implemented keyframe properties dialog Allows finer control over the keyframes and their bezier control handles. --- app/dialog/CMakeLists.txt | 1 + app/dialog/keyframeproperties/CMakeLists.txt | 22 ++ .../keyframeproperties/keyframeproperties.cpp | 223 ++++++++++++++++++ .../keyframeproperties/keyframeproperties.h | 47 ++++ 4 files changed, 293 insertions(+) create mode 100644 app/dialog/keyframeproperties/CMakeLists.txt create mode 100644 app/dialog/keyframeproperties/keyframeproperties.cpp create mode 100644 app/dialog/keyframeproperties/keyframeproperties.h diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index 09fa3653b..f5683a0e3 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -18,6 +18,7 @@ add_subdirectory(about) add_subdirectory(actionsearch) add_subdirectory(export) add_subdirectory(footageproperties) +add_subdirectory(keyframeproperties) add_subdirectory(preferences) add_subdirectory(projectproperties) add_subdirectory(sequence) diff --git a/app/dialog/keyframeproperties/CMakeLists.txt b/app/dialog/keyframeproperties/CMakeLists.txt new file mode 100644 index 000000000..fc3b399ec --- /dev/null +++ b/app/dialog/keyframeproperties/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2019 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + dialog/keyframeproperties/keyframeproperties.h + dialog/keyframeproperties/keyframeproperties.cpp + PARENT_SCOPE +) diff --git a/app/dialog/keyframeproperties/keyframeproperties.cpp b/app/dialog/keyframeproperties/keyframeproperties.cpp new file mode 100644 index 000000000..6507bee66 --- /dev/null +++ b/app/dialog/keyframeproperties/keyframeproperties.cpp @@ -0,0 +1,223 @@ +#include "keyframeproperties.h" + +#include +#include + +#include "core.h" +#include "common/timecodefunctions.h" +#include "widget/keyframeview/keyframeviewundo.h" +#include "widget/nodeparamview/nodeparamviewundo.h" + +KeyframePropertiesDialog::KeyframePropertiesDialog(const QList &keys, const rational &timebase, QWidget *parent) : + QDialog(parent), + keys_(keys), + timebase_(timebase) +{ + setWindowTitle(tr("Keyframe Properties")); + + QGridLayout* layout = new QGridLayout(this); + + int row = 0; + + layout->addWidget(new QLabel("Time:"), row, 0); + + time_slider_ = new TimeSlider(); + time_slider_->SetTimebase(timebase_); + layout->addWidget(time_slider_, row, 1); + + row++; + + layout->addWidget(new QLabel("Type:"), row, 0); + + type_select_ = new QComboBox(); + connect(type_select_, SIGNAL(currentIndexChanged(int)), this, SLOT(KeyTypeChanged(int))); + layout->addWidget(type_select_, row, 1); + + row++; + + // Bezier handles + bezier_group_ = new QGroupBox(); + + QGridLayout* bezier_group_layout = new QGridLayout(bezier_group_); + + bezier_group_layout->addWidget(new QLabel(tr("In:")), 0, 0); + + bezier_in_x_slider_ = new FloatSlider(); + bezier_group_layout->addWidget(bezier_in_x_slider_, 0, 1); + + bezier_in_y_slider_ = new FloatSlider(); + bezier_group_layout->addWidget(bezier_in_y_slider_, 0, 2); + + bezier_group_layout->addWidget(new QLabel(tr("Out:")), 1, 0); + + bezier_out_x_slider_ = new FloatSlider(); + bezier_group_layout->addWidget(bezier_out_x_slider_, 1, 1); + + bezier_out_y_slider_ = new FloatSlider(); + bezier_group_layout->addWidget(bezier_out_y_slider_, 1, 2); + + layout->addWidget(bezier_group_, row, 0, 1, 2); + + bool all_same_time = true; + bool can_set_time = true; + + bool all_same_type = true; + + bool all_same_bezier_in_x = true; + bool all_same_bezier_in_y = true; + bool all_same_bezier_out_x = true; + bool all_same_bezier_out_y = true; + + for (int i=0;i 0) { + NodeKeyframePtr prev_key = keys_.at(i-1); + NodeKeyframePtr this_key = keys_.at(i); + + // Determine if the keyframes are all the same time or not + if (all_same_time) { + all_same_time = (prev_key->time() == this_key->time()); + } + + // Determine if the keyframes are all the same type + if (all_same_type) { + all_same_type = (prev_key->type() == this_key->type()); + } + + // Check all four bezier control points + if (all_same_bezier_in_x) { + all_same_bezier_in_x = (prev_key->bezier_control_in().x() == this_key->bezier_control_in().x()); + } + + if (all_same_bezier_in_y) { + all_same_bezier_in_y = (prev_key->bezier_control_in().y() == this_key->bezier_control_in().y()); + } + + if (all_same_bezier_out_x) { + all_same_bezier_out_x = (prev_key->bezier_control_out().x() == this_key->bezier_control_out().x()); + } + + if (all_same_bezier_out_y) { + all_same_bezier_out_y = (prev_key->bezier_control_out().y() == this_key->bezier_control_out().y()); + } + } + + // Determine if any keyframes are on the same track (in which case we can't set the time) + if (can_set_time) { + for (int j=0;jtrack() == keys_.at(i)->track()); + } + + if (!can_set_time) { + break; + } + } + } + + if (!all_same_time + && !all_same_type + && !can_set_time + && !all_same_bezier_in_x + && !all_same_bezier_in_y + && !all_same_bezier_out_x + && !all_same_bezier_out_y) { + break; + } + } + + if (all_same_time) { + time_slider_->SetValue(Timecode::time_to_timestamp(keys_.first()->time(), timebase_)); + } else { + time_slider_->SetTristate(); + } + + if (!can_set_time) { + time_slider_->setEnabled(false); + } + + if (!all_same_type) { + // If all keyframes aren't the same type, add an empty item + type_select_->addItem(QStringLiteral("--"), -1); + + // Ensure UI updates for the index being 0 + KeyTypeChanged(0); + } + + type_select_->addItem(tr("Linear"), NodeKeyframe::kLinear); + type_select_->addItem(tr("Hold"), NodeKeyframe::kHold); + type_select_->addItem(tr("Bezier"), NodeKeyframe::kBezier); + + if (all_same_type) { + // If all keyframes are the same type, set it here + for (int i=0;icount();i++) { + if (type_select_->itemData(i).toInt() == keys_.first()->type()) { + type_select_->setCurrentIndex(i); + + // Ensure UI updates for this index + KeyTypeChanged(i); + break; + } + } + } + + SetUpBezierSlider(bezier_in_x_slider_, all_same_bezier_in_x, keys_.first()->bezier_control_in().x()); + SetUpBezierSlider(bezier_in_y_slider_, all_same_bezier_in_y, keys_.first()->bezier_control_in().y()); + SetUpBezierSlider(bezier_out_x_slider_, all_same_bezier_out_x, keys_.first()->bezier_control_out().x()); + SetUpBezierSlider(bezier_out_y_slider_, all_same_bezier_out_y, keys_.first()->bezier_control_out().y()); + + row++; + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + buttons->setCenterButtons(true); + layout->addWidget(buttons, row, 0, 1, 2); + connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +void KeyframePropertiesDialog::accept() +{ + QUndoCommand* command = new QUndoCommand(); + + rational new_time = Timecode::timestamp_to_time(time_slider_->GetValue(), timebase_); + int new_type = type_select_->currentData().toInt(); + + foreach (NodeKeyframePtr key, keys_) { + if (time_slider_->isEnabled() && !time_slider_->IsTristate()) { + new NodeParamSetKeyframeTimeCommand(key, new_time, command); + } + + if (new_type > -1) { + new KeyframeSetTypeCommand(key, static_cast(new_type), command); + } + + if (bezier_group_->isEnabled()) { + new KeyframeSetBezierControlPoint(key, + NodeKeyframe::kInHandle, + QPointF(bezier_in_x_slider_->GetValue(), bezier_in_y_slider_->GetValue()), + command); + + new KeyframeSetBezierControlPoint(key, + NodeKeyframe::kOutHandle, + QPointF(bezier_out_x_slider_->GetValue(), bezier_out_y_slider_->GetValue()), + command); + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); + + QDialog::accept(); +} + +void KeyframePropertiesDialog::SetUpBezierSlider(FloatSlider *slider, bool all_same, double value) +{ + if (all_same) { + slider->SetValue(value); + } else { + slider->SetTristate(); + } +} + +void KeyframePropertiesDialog::KeyTypeChanged(int index) +{ + bezier_group_->setEnabled(type_select_->itemData(index) == NodeKeyframe::kBezier); +} diff --git a/app/dialog/keyframeproperties/keyframeproperties.h b/app/dialog/keyframeproperties/keyframeproperties.h new file mode 100644 index 000000000..415365b5d --- /dev/null +++ b/app/dialog/keyframeproperties/keyframeproperties.h @@ -0,0 +1,47 @@ +#ifndef KEYFRAMEPROPERTIESDIALOG_H +#define KEYFRAMEPROPERTIESDIALOG_H + +#include +#include +#include + +#include "node/keyframe.h" +#include "widget/slider/floatslider.h" +#include "widget/slider/timeslider.h" + +class KeyframePropertiesDialog : public QDialog +{ + Q_OBJECT +public: + KeyframePropertiesDialog(const QList& keys, const rational& timebase, QWidget* parent = nullptr); + +public slots: + virtual void accept() override; + +private: + void SetUpBezierSlider(FloatSlider *slider, bool all_same, double value); + + const QList& keys_; + + rational timebase_; + + TimeSlider* time_slider_; + + QComboBox* type_select_; + + QGroupBox* bezier_group_; + + FloatSlider* bezier_in_x_slider_; + + FloatSlider* bezier_in_y_slider_; + + FloatSlider* bezier_out_x_slider_; + + FloatSlider* bezier_out_y_slider_; + +private slots: + void KeyTypeChanged(int index); + +}; + +#endif // KEYFRAMEPROPERTIESDIALOG_H