diff --git a/app/common/lerp.h b/app/common/lerp.h index d62e97ddd..da1f95b8a 100644 --- a/app/common/lerp.h +++ b/app/common/lerp.h @@ -32,4 +32,9 @@ T lerp(T a, T b, double t) { return (a * (1.0 - t)) + (b * t); } +template +T lerp(T a, T b, float t) { + return (a * (1.0f - t)) + (b * t); +} + #endif // LERP_H diff --git a/app/node/input.cpp b/app/node/input.cpp index 236a31696..1f20d7bc8 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -20,6 +20,10 @@ #include "input.h" +#include +#include +#include + #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(), after->value().value(), static_cast(period_progress)); + break; + case kVec3: + interpolated_value = lerp(before->value().value(), after->value().value(), static_cast(period_progress)); + break; + case kVec4: + interpolated_value = lerp(before->value().value(), after->value().value(), static_cast(period_progress)); + break; + default: + interpolated_value = before->value(); + } return interpolated_value; } diff --git a/app/node/input.h b/app/node/input.h index a85875b4c..fb1907451 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -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 diff --git a/app/widget/keyframeview/keyframeviewundo.cpp b/app/widget/keyframeview/keyframeviewundo.cpp new file mode 100644 index 000000000..8c1a843b6 --- /dev/null +++ b/app/widget/keyframeview/keyframeviewundo.cpp @@ -0,0 +1,6 @@ +#include "keyframeviewundo.h" + +KeyframeViewUndo::KeyframeViewUndo() +{ + +} diff --git a/app/widget/keyframeview/keyframeviewundo.h b/app/widget/keyframeview/keyframeviewundo.h new file mode 100644 index 000000000..866b0d1e6 --- /dev/null +++ b/app/widget/keyframeview/keyframeviewundo.h @@ -0,0 +1,11 @@ +#ifndef KEYFRAMEVIEWUNDO_H +#define KEYFRAMEVIEWUNDO_H + + +class KeyframeViewUndo +{ +public: + KeyframeViewUndo(); +}; + +#endif // KEYFRAMEVIEWUNDO_H diff --git a/app/widget/nodeparamview/nodeparamviewundo.cpp b/app/widget/nodeparamview/nodeparamviewundo.cpp index 3f4095413..c981be51a 100644 --- a/app/widget/nodeparamview/nodeparamviewundo.cpp +++ b/app/widget/nodeparamview/nodeparamviewundo.cpp @@ -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_); diff --git a/app/widget/nodeparamview/nodeparamviewundo.h b/app/widget/nodeparamview/nodeparamviewundo.h index 44ffc881b..3691f4f60 100644 --- a/app/widget/nodeparamview/nodeparamviewundo.h +++ b/app/widget/nodeparamview/nodeparamviewundo.h @@ -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; diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index fd714fe8d..6edb2e17d 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -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(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(sender())->GetValue()); + IntegerSlider* slider = static_cast(sender()); + + ProcessSlider(slider, slider->GetValue()); break; } case NodeParam::kFloat: { // Widget is a FloatSlider - SetInputValue(static_cast(sender())->GetValue()); + FloatSlider* slider = static_cast(sender()); + + ProcessSlider(slider, slider->GetValue()); break; } case NodeParam::kVec2: - SetInputValue(QVector2D( - static_cast(static_cast(widgets_.at(0))->GetValue()), - static_cast(static_cast(widgets_.at(1))->GetValue()) - )); + { + // Widget is a FloatSlider + FloatSlider* slider = static_cast(sender()); + + QVector2D value( + static_cast(static_cast(widgets_.at(0))->GetValue()), + static_cast(static_cast(widgets_.at(1))->GetValue()) + ); + + ProcessSlider(slider, value); break; + } case NodeParam::kVec3: - // Widgets are three FloatSliders - SetInputValue(QVector3D( - static_cast(static_cast(widgets_.at(0))->GetValue()), - static_cast(static_cast(widgets_.at(1))->GetValue()), - static_cast(static_cast(widgets_.at(2))->GetValue()) - )); + { + // Widget is a FloatSlider + FloatSlider* slider = static_cast(sender()); + + QVector3D value( + static_cast(static_cast(widgets_.at(0))->GetValue()), + static_cast(static_cast(widgets_.at(1))->GetValue()), + static_cast(static_cast(widgets_.at(2))->GetValue()) + ); + + ProcessSlider(slider, value); break; + } case NodeParam::kVec4: { - // Widgets are three FloatSliders - SetInputValue(QVector4D( - static_cast(static_cast(widgets_.at(0))->GetValue()), - static_cast(static_cast(widgets_.at(1))->GetValue()), - static_cast(static_cast(widgets_.at(2))->GetValue()), - static_cast(static_cast(widgets_.at(3))->GetValue()) - )); + // Widget is a FloatSlider + FloatSlider* slider = static_cast(sender()); + + QVector4D value( + static_cast(static_cast(widgets_.at(0))->GetValue()), + static_cast(static_cast(widgets_.at(1))->GetValue()), + static_cast(static_cast(widgets_.at(2))->GetValue()), + static_cast(static_cast(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_); + } +} diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h index e4898a89f..4fff55bd2 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h @@ -4,6 +4,7 @@ #include #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 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