fully implemented setting keyframes and standard values in the NodeParamView

Widget now supports creating keyframe and standard values, supports dragging
from sliders (not creating an undo command for each drag), and everything is
undoable.
This commit is contained in:
itsmattkc
2019-12-27 14:51:21 +11:00
parent 9272858de2
commit 855fe5e93f
9 changed files with 234 additions and 24 deletions
+5
View File
@@ -32,4 +32,9 @@ T lerp(T a, T b, double t) {
return (a * (1.0 - t)) + (b * t);
}
template<typename T>
T lerp(T a, T b, float t) {
return (a * (1.0f - t)) + (b * t);
}
#endif // LERP_H
+33 -2
View File
@@ -20,6 +20,10 @@
#include "input.h"
#include <QVector2D>
#include <QVector3D>
#include <QVector4D>
#include "common/lerp.h"
#include "node.h"
#include "output.h"
@@ -81,6 +85,15 @@ Node *NodeInput::get_connected_node() const
return nullptr;
}
bool NodeInput::type_can_be_interpolated(NodeParam::DataType type)
{
return type == kFloat
|| type == kVec2
|| type == kVec3
|| type == kVec4
|| type == kColor;
}
QVariant NodeInput::get_value_at_time(const rational &time) const
{
if (is_using_standard_value()) {
@@ -103,7 +116,7 @@ QVariant NodeInput::get_value_at_time(const rational &time) const
NodeKeyframePtr after = keyframes_.at(i+1);
if (before->time() == time
|| data_type() != kFloat // FIXME: Expand this to other types that can be interpolated
|| !type_can_be_interpolated(data_type())
|| (before->time() < time && before->type() == NodeKeyframe::kHold)) {
// Time == keyframe time, so value is precise
@@ -121,7 +134,25 @@ QVariant NodeInput::get_value_at_time(const rational &time) const
} else {
// To have arrived here, the keyframes must both be linear
qreal period_progress = (time.toDouble() - before->time().toDouble()) / (after->time().toDouble() - before->time().toDouble());
qreal interpolated_value = lerp(before->value().toDouble(), after->value().toDouble(), period_progress);
QVariant interpolated_value;
switch (data_type()) {
case kFloat:
interpolated_value = lerp(before->value().toDouble(), after->value().toDouble(), period_progress);
break;
case kVec2:
interpolated_value = lerp(before->value().value<QVector2D>(), after->value().value<QVector2D>(), static_cast<float>(period_progress));
break;
case kVec3:
interpolated_value = lerp(before->value().value<QVector3D>(), after->value().value<QVector3D>(), static_cast<float>(period_progress));
break;
case kVec4:
interpolated_value = lerp(before->value().value<QVector4D>(), after->value().value<QVector4D>(), static_cast<float>(period_progress));
break;
default:
interpolated_value = before->value();
}
return interpolated_value;
}
+5
View File
@@ -179,6 +179,11 @@ signals:
void KeyframeRemoved(NodeKeyframePtr key);
private:
/**
* @brief Returns whether a data type can be interpolated or not
*/
static bool type_can_be_interpolated(DataType type);
/**
* @brief We use Qt signals/slots for keyframe communication but store them as shared ptrs. This function converts
* a raw ptr to a list index
@@ -0,0 +1,6 @@
#include "keyframeviewundo.h"
KeyframeViewUndo::KeyframeViewUndo()
{
}
@@ -0,0 +1,11 @@
#ifndef KEYFRAMEVIEWUNDO_H
#define KEYFRAMEVIEWUNDO_H
class KeyframeViewUndo
{
public:
KeyframeViewUndo();
};
#endif // KEYFRAMEVIEWUNDO_H
+31 -2
View File
@@ -26,6 +26,15 @@ NodeParamSetKeyframeValueCommand::NodeParamSetKeyframeValueCommand(NodeKeyframeP
{
}
NodeParamSetKeyframeValueCommand::NodeParamSetKeyframeValueCommand(NodeKeyframePtr key, const QVariant &new_value, const QVariant &old_value, QUndoCommand *parent) :
QUndoCommand(parent),
key_(key),
old_value_(old_value),
new_value_(new_value)
{
}
void NodeParamSetKeyframeValueCommand::redo()
{
key_->set_value(new_value_);
@@ -39,18 +48,30 @@ void NodeParamSetKeyframeValueCommand::undo()
NodeParamInsertKeyframeCommand::NodeParamInsertKeyframeCommand(NodeInput *input, NodeKeyframePtr keyframe, QUndoCommand* parent) :
QUndoCommand(parent),
input_(input),
keyframe_(keyframe)
keyframe_(keyframe),
done_(false)
{
}
NodeParamInsertKeyframeCommand::NodeParamInsertKeyframeCommand(NodeInput *input, NodeKeyframePtr keyframe, bool already_done, QUndoCommand *parent) :
QUndoCommand(parent),
input_(input),
keyframe_(keyframe),
done_(already_done)
{
}
void NodeParamInsertKeyframeCommand::redo()
{
input_->insert_keyframe(keyframe_);
if (!done_) {
input_->insert_keyframe(keyframe_);
}
}
void NodeParamInsertKeyframeCommand::undo()
{
input_->remove_keyframe(keyframe_);
done_ = false;
}
NodeParamRemoveKeyframeCommand::NodeParamRemoveKeyframeCommand(NodeInput *input, NodeKeyframePtr keyframe, QUndoCommand *parent) :
@@ -96,6 +117,14 @@ NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(NodeInput *in
{
}
NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(NodeInput *input, const QVariant &new_value, const QVariant &old_value, QUndoCommand *parent) :
QUndoCommand(parent),
input_(input),
old_value_(old_value),
new_value_(new_value)
{
}
void NodeParamSetStandardValueCommand::redo()
{
input_->set_standard_value(new_value_);
@@ -20,6 +20,7 @@ private:
class NodeParamInsertKeyframeCommand : public QUndoCommand {
public:
NodeParamInsertKeyframeCommand(NodeInput* input, NodeKeyframePtr keyframe, QUndoCommand *parent = nullptr);
NodeParamInsertKeyframeCommand(NodeInput* input, NodeKeyframePtr keyframe, bool already_done, QUndoCommand *parent = nullptr);
virtual void redo() override;
virtual void undo() override;
@@ -29,6 +30,8 @@ private:
NodeKeyframePtr keyframe_;
bool done_;
};
class NodeParamRemoveKeyframeCommand : public QUndoCommand {
@@ -63,6 +66,7 @@ private:
class NodeParamSetKeyframeValueCommand : public QUndoCommand {
public:
NodeParamSetKeyframeValueCommand(NodeKeyframePtr key, const QVariant& value, QUndoCommand* parent = nullptr);
NodeParamSetKeyframeValueCommand(NodeKeyframePtr key, const QVariant& new_value, const QVariant& old_value, QUndoCommand* parent = nullptr);
virtual void redo() override;
virtual void undo() override;
@@ -78,6 +82,7 @@ private:
class NodeParamSetStandardValueCommand : public QUndoCommand {
public:
NodeParamSetStandardValueCommand(NodeInput* input, const QVariant& value, QUndoCommand* parent = nullptr);
NodeParamSetStandardValueCommand(NodeInput* input, const QVariant& new_value, const QVariant& old_value, QUndoCommand* parent = nullptr);
virtual void redo() override;
virtual void undo() override;
@@ -18,9 +18,13 @@
NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(NodeInput *input, QObject *parent) :
QObject(parent),
input_(input)
input_(input),
dragging_(false),
drag_created_keyframe_(false)
{
CreateWidgets();
connect(input_, &NodeInput::ValueChanged, this, &NodeParamViewWidgetBridge::InputValueChanged);
}
void NodeParamViewWidgetBridge::SetTime(const rational &time)
@@ -267,6 +271,80 @@ void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value)
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
void NodeParamViewWidgetBridge::ProcessSlider(SliderBase *slider, const QVariant &value)
{
if (slider->IsDragging()) {
// While we're dragging, we block the input's normal signalling and create our own
input_->blockSignals(true);
if (!dragging_) {
// Set up new drag
dragging_ = true;
// Cache current value
drag_old_value_ = input_->get_value_at_time(time_);
// Determine whether we are creating a keyframe or not
if (input_->is_keyframing()) {
dragging_keyframe_ = input_->get_keyframe_at_time(time_);
drag_created_keyframe_ = !dragging_keyframe_;
if (drag_created_keyframe_) {
dragging_keyframe_ = std::make_shared<NodeKeyframe>(time_,
value,
input_->get_best_keyframe_type_for_time(time_));
input_->insert_keyframe(dragging_keyframe_);
// We re-enable signals temporarily to emit the keyframe added signal
input_->blockSignals(false);
emit input_->KeyframeAdded(dragging_keyframe_);
input_->blockSignals(true);
}
}
}
if (input_->is_keyframing()) {
dragging_keyframe_->set_value(value);
} else {
input_->set_standard_value(value);
}
input_->blockSignals(false);
emit input_->ValueChanged(time_, time_);
} else {
if (dragging_) {
// We were dragging and just stopped
dragging_ = false;
QUndoCommand* command = new QUndoCommand();
if (input_->is_keyframing()) {
if (drag_created_keyframe_) {
// We created a keyframe in this process
new NodeParamInsertKeyframeCommand(input_, dragging_keyframe_, true, command);
}
// We just set a keyframe's value
// We do this even when inserting a keyframe because we don't actually perform an insert in this undo command
// so this will ensure the ValueChanged() signal is sent correctly
new NodeParamSetKeyframeValueCommand(dragging_keyframe_, value, drag_old_value_, command);
} else {
// We just set the standard value
new NodeParamSetStandardValueCommand(input_, value, drag_old_value_, command);
}
Core::instance()->undo_stack()->push(command);
} else {
// No drag was involved, we can just push the value
SetInputValue(value);
}
}
}
void NodeParamViewWidgetBridge::WidgetCallback()
{
switch (input_->data_type()) {
@@ -287,38 +365,59 @@ void NodeParamViewWidgetBridge::WidgetCallback()
case NodeParam::kInt:
{
// Widget is a IntegerSlider
SetInputValue(static_cast<IntegerSlider*>(sender())->GetValue());
IntegerSlider* slider = static_cast<IntegerSlider*>(sender());
ProcessSlider(slider, slider->GetValue());
break;
}
case NodeParam::kFloat:
{
// Widget is a FloatSlider
SetInputValue(static_cast<FloatSlider*>(sender())->GetValue());
FloatSlider* slider = static_cast<FloatSlider*>(sender());
ProcessSlider(slider, slider->GetValue());
break;
}
case NodeParam::kVec2:
SetInputValue(QVector2D(
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(0))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(1))->GetValue())
));
{
// Widget is a FloatSlider
FloatSlider* slider = static_cast<FloatSlider*>(sender());
QVector2D value(
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(0))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(1))->GetValue())
);
ProcessSlider(slider, value);
break;
}
case NodeParam::kVec3:
// Widgets are three FloatSliders
SetInputValue(QVector3D(
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(0))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(1))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(2))->GetValue())
));
{
// Widget is a FloatSlider
FloatSlider* slider = static_cast<FloatSlider*>(sender());
QVector3D value(
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(0))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(1))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(2))->GetValue())
);
ProcessSlider(slider, value);
break;
}
case NodeParam::kVec4:
{
// Widgets are three FloatSliders
SetInputValue(QVector4D(
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(0))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(1))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(2))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(3))->GetValue())
));
// Widget is a FloatSlider
FloatSlider* slider = static_cast<FloatSlider*>(sender());
QVector4D value(
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(0))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(1))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(2))->GetValue()),
static_cast<float>(static_cast<FloatSlider*>(widgets_.at(3))->GetValue())
);
ProcessSlider(slider, value);
break;
}
case NodeParam::kFile:
@@ -353,3 +452,11 @@ void NodeParamViewWidgetBridge::WidgetCallback()
}
}
}
void NodeParamViewWidgetBridge::InputValueChanged(const rational &start, const rational &end)
{
if (!dragging_ && start <= time_ && end >= time_) {
// We'll need to update the widgets because the values have changed on our current time
SetTime(time_);
}
}
@@ -4,6 +4,7 @@
#include <QObject>
#include "node/input.h"
#include "widget/slider/sliderbase.h"
class NodeParamViewWidgetBridge : public QObject
{
@@ -20,14 +21,24 @@ private:
void SetInputValue(const QVariant& value);
void ProcessSlider(SliderBase* slider, const QVariant& value);
NodeInput* input_;
QList<QWidget*> widgets_;
rational time_;
bool dragging_;
bool drag_created_keyframe_;
QVariant drag_old_value_;
NodeKeyframePtr dragging_keyframe_;
private slots:
void WidgetCallback();
void InputValueChanged(const rational& start, const rational& end);
};
#endif // NODEPARAMVIEWWIDGETBRIDGE_H