From 855fe5e93f9f0b387198d31b787dc9966dd256b4 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 27 Dec 2019 14:51:21 +1100 Subject: [PATCH] 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. --- app/common/lerp.h | 5 + app/node/input.cpp | 35 ++++- app/node/input.h | 5 + app/widget/keyframeview/keyframeviewundo.cpp | 6 + app/widget/keyframeview/keyframeviewundo.h | 11 ++ .../nodeparamview/nodeparamviewundo.cpp | 33 +++- app/widget/nodeparamview/nodeparamviewundo.h | 5 + .../nodeparamviewwidgetbridge.cpp | 147 +++++++++++++++--- .../nodeparamview/nodeparamviewwidgetbridge.h | 11 ++ 9 files changed, 234 insertions(+), 24 deletions(-) create mode 100644 app/widget/keyframeview/keyframeviewundo.cpp create mode 100644 app/widget/keyframeview/keyframeviewundo.h 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