diff --git a/app/node/input.cpp b/app/node/input.cpp index e5ee326c8..17222030e 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -479,8 +479,7 @@ void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_conn // Copy keyframes dest->keyframes_.clear(); foreach (NodeKeyframePtr key, source->keyframes_) { - NodeKeyframePtr copy = std::make_shared(key->time(), key->value(), key->type()); - dest->keyframes_.append(copy); + dest->keyframes_.append(key->copy()); } // Copy keyframing state diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index facfd60f0..9f07c2085 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -20,20 +20,28 @@ #include "keyframe.h" -NodeKeyframe::NodeKeyframe() : - time_(0), - value_(0), - type_(kLinear) -{ -} - NodeKeyframe::NodeKeyframe(const rational &time, const QVariant &value, const NodeKeyframe::Type &type) : time_(time), value_(value), - type_(type) + type_(type), + bezier_control_in_(QPointF(-1.0, 0.0)), + bezier_control_out_(QPointF(1.0, 0.0)) { } +NodeKeyframePtr NodeKeyframe::Create(const rational &time, const QVariant &value, const NodeKeyframe::Type &type) +{ + return std::make_shared(time, value, type); +} + +NodeKeyframePtr NodeKeyframe::copy() const +{ + NodeKeyframePtr copy = std::make_shared(time_, value_, type_); + copy->bezier_control_in_ = bezier_control_in_; + copy->bezier_control_out_ = bezier_control_out_; + return copy; +} + const rational &NodeKeyframe::time() const { return time_; @@ -66,3 +74,52 @@ void NodeKeyframe::set_type(const NodeKeyframe::Type &type) type_ = type; emit TypeChanged(type_); } + +const QPointF &NodeKeyframe::bezier_control_in() const +{ + return bezier_control_in_; +} + +void NodeKeyframe::set_bezier_control_in(const QPointF &control) +{ + bezier_control_in_ = control; + emit BezierControlInChanged(bezier_control_in_); +} + +const QPointF &NodeKeyframe::bezier_control_out() const +{ + return bezier_control_out_; +} + +void NodeKeyframe::set_bezier_control_out(const QPointF &control) +{ + bezier_control_out_ = control; + emit BezierControlOutChanged(bezier_control_out_); +} + +const QPointF &NodeKeyframe::bezier_control(NodeKeyframe::BezierType type) const +{ + if (type == kInHandle) { + return bezier_control_in(); + } else { + return bezier_control_out(); + } +} + +void NodeKeyframe::set_bezier_control(NodeKeyframe::BezierType type, const QPointF &control) +{ + if (type == kInHandle) { + set_bezier_control_in(control); + } else { + set_bezier_control_out(control); + } +} + +NodeKeyframe::BezierType NodeKeyframe::get_opposing_bezier_type(NodeKeyframe::BezierType type) +{ + if (type == kInHandle) { + return kOutHandle; + } else { + return kInHandle; + } +} diff --git a/app/node/keyframe.h b/app/node/keyframe.h index 7cb556980..9a3d3a6de 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -36,7 +36,6 @@ class NodeKeyframe : public QObject { Q_OBJECT public: - /** * @brief Methods of interpolation to use with this keyframe */ @@ -46,18 +45,25 @@ public: kBezier }; - static const Type kDefaultType = kLinear; - /** - * @brief NodeKeyframe Constructor + * @brief The two types of bezier handles that are available on bezier keyframes */ - NodeKeyframe(); + enum BezierType { + kInHandle, + kOutHandle + }; /** * @brief NodeKeyframe Constructor */ NodeKeyframe(const rational& time, const QVariant& value, const Type& type); + static const Type kDefaultType = kLinear; + + static NodeKeyframePtr Create(const rational& time, const QVariant& value, const Type& type); + + NodeKeyframePtr copy() const; + /** * @brief The time this keyframe is set at */ @@ -76,6 +82,26 @@ public: const Type& type() const; void set_type(const Type& type); + /** + * @brief For bezier interpolation, the control point leading into this keyframe + */ + const QPointF &bezier_control_in() const; + void set_bezier_control_in(const QPointF& control); + + /** + * @brief For bezier interpolation, the control point leading out of this keyframe + */ + const QPointF& bezier_control_out() const; + void set_bezier_control_out(const QPointF& control); + + /** + * @brief Convenience functions for retrieving/setting bezier handle information with a BezierType + */ + const QPointF& bezier_control(BezierType type) const; + void set_bezier_control(BezierType type, const QPointF& control); + + static BezierType get_opposing_bezier_type(BezierType type); + signals: /** * @brief Signal emitted when this keyframe's time is changed @@ -92,12 +118,26 @@ signals: */ void TypeChanged(const Type& type); + /** + * @brief Signal emitted when this keyframe's bezier in control point is changed + */ + void BezierControlInChanged(const QPointF& d); + + /** + * @brief Signal emitted when this keyframe's bezier out control point is changed + */ + void BezierControlOutChanged(const QPointF& d); + private: rational time_; QVariant value_; Type type_; + + QPointF bezier_control_in_; + + QPointF bezier_control_out_; }; Q_DECLARE_METATYPE(NodeKeyframe::Type) diff --git a/app/widget/curvewidget/CMakeLists.txt b/app/widget/curvewidget/CMakeLists.txt index 69be102f4..52cf1cdf6 100644 --- a/app/widget/curvewidget/CMakeLists.txt +++ b/app/widget/curvewidget/CMakeLists.txt @@ -16,6 +16,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} + widget/curvewidget/beziercontrolpointitem.h + widget/curvewidget/beziercontrolpointitem.cpp widget/curvewidget/curveview.h widget/curvewidget/curveview.cpp widget/curvewidget/curvewidget.h diff --git a/app/widget/curvewidget/beziercontrolpointitem.cpp b/app/widget/curvewidget/beziercontrolpointitem.cpp new file mode 100644 index 000000000..6d19d0313 --- /dev/null +++ b/app/widget/curvewidget/beziercontrolpointitem.cpp @@ -0,0 +1,80 @@ +#include "beziercontrolpointitem.h" + +#include +#include +#include +#include + +#include "common/qtversionabstraction.h" + +BezierControlPointItem::BezierControlPointItem(NodeKeyframePtr key, NodeKeyframe::BezierType mode, QGraphicsItem *parent) : + QGraphicsRectItem(parent), + key_(key), + mode_(mode), + x_scale_(1.0), + y_scale_(1.0) +{ + setFlag(QGraphicsItem::ItemIsMovable); + + connect(key.get(), &NodeKeyframe::TimeChanged, this, &BezierControlPointItem::UpdatePos); + + if (mode_ == NodeKeyframe::kInHandle) { + connect(key.get(), &NodeKeyframe::BezierControlInChanged, this, &BezierControlPointItem::UpdatePos); + } else { + connect(key.get(), &NodeKeyframe::BezierControlOutChanged, this, &BezierControlPointItem::UpdatePos); + } + + + int control_point_size = QFontMetricsWidth(qApp->fontMetrics(), "o"); + int half_sz = control_point_size / 2; + setRect(-half_sz, -half_sz, control_point_size, control_point_size); +} + +void BezierControlPointItem::SetXScale(double scale) +{ + x_scale_ = scale; + UpdatePos(); +} + +void BezierControlPointItem::SetYScale(double scale) +{ + y_scale_ = scale; + UpdatePos(); +} + +NodeKeyframePtr BezierControlPointItem::key() const +{ + return key_; +} + +const NodeKeyframe::BezierType &BezierControlPointItem::mode() const +{ + return mode_; +} + +QPointF BezierControlPointItem::GetCorrespondingKeyframeHandle() const +{ + return key_->bezier_control(mode_); +} + +void BezierControlPointItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + if (option->state & QStyle::State_Selected) { + painter->setPen(widget->palette().highlight().color()); + } else { + painter->setPen(widget->palette().text().color()); + } + + painter->drawEllipse(rect()); +} + +void BezierControlPointItem::UpdatePos() +{ + QPointF handle_offset = GetCorrespondingKeyframeHandle(); + + // Scale handle offset + handle_offset.setX(handle_offset.x() * x_scale_); + handle_offset.setY(handle_offset.y() * y_scale_); + + setPos(handle_offset - rect().center()); +} diff --git a/app/widget/curvewidget/beziercontrolpointitem.h b/app/widget/curvewidget/beziercontrolpointitem.h new file mode 100644 index 000000000..fbb0d89b8 --- /dev/null +++ b/app/widget/curvewidget/beziercontrolpointitem.h @@ -0,0 +1,44 @@ +#ifndef BEZIERCONTROLPOINTITEM_H +#define BEZIERCONTROLPOINTITEM_H + +#include + +#include "node/keyframe.h" + +class BezierControlPointItem : public QObject, public QGraphicsRectItem +{ +public: + BezierControlPointItem(NodeKeyframePtr key, NodeKeyframe::BezierType mode, QGraphicsItem* parent = nullptr); + + void SetXScale(double scale); + + void SetYScale(double scale); + + NodeKeyframePtr key() const; + + const NodeKeyframe::BezierType& mode() const; + + QPointF GetCorrespondingKeyframeHandle() const; + + void SetCorrespondingKeyframeHandle(const QPointF& handle); + + void SetOpposingKeyframeHandle(const QPointF& handle); + +protected: + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + +private: + NodeKeyframePtr key_; + + NodeKeyframe::BezierType mode_; + + double x_scale_; + + double y_scale_; + +private slots: + void UpdatePos(); + +}; + +#endif // BEZIERCONTROLPOINTITEM_H diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index 7a10eb950..0358f7a25 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -1,5 +1,6 @@ #include "curveview.h" +#include #include #include "common/qtversionabstraction.h" @@ -15,6 +16,14 @@ CurveView::CurveView(QWidget *parent) : text_padding_ = QFontMetricsWidth(fontMetrics(), QStringLiteral("i")); minimum_grid_space_ = QFontMetricsWidth(fontMetrics(), QStringLiteral("00000")); + + connect(scene(), &QGraphicsScene::selectionChanged, this, &CurveView::SelectionChanged); +} + +CurveView::~CurveView() +{ + // Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after other memebers have been destroyed + Clear(); } void CurveView::Clear() @@ -24,7 +33,6 @@ void CurveView::Clear() foreach (QGraphicsLineItem* line, lines_) { delete line; } - lines_.clear(); } @@ -40,6 +48,8 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) return; } + painter->setRenderHint(QPainter::Antialiasing); + QVector lines; double x_interval = timebase().flipped().toDouble(); @@ -89,7 +99,7 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) QVector keyframe_lines; // Draw straight line leading to first keyframe - QPointF first_key_pos = item_map().value(keys.first())->center_pos(); + QPointF first_key_pos = item_map().value(keys.first())->pos(); keyframe_lines.append(QLineF(QPointF(scene_bottom_left.x(), first_key_pos.y()), first_key_pos)); // Draw lines between each keyframe @@ -102,30 +112,76 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) if (before->type() == NodeKeyframe::kHold) { // Draw a hold keyframe (basically a right angle) - keyframe_lines.append(QLineF(before_item->center_pos().x(), - before_item->center_pos().y(), - after_item->center_pos().x(), - before_item->center_pos().y())); - keyframe_lines.append(QLineF(after_item->center_pos().x(), - before_item->center_pos().y(), - after_item->center_pos().x(), - after_item->center_pos().y())); + keyframe_lines.append(QLineF(before_item->pos().x(), + before_item->pos().y(), + after_item->pos().x(), + before_item->pos().y())); + keyframe_lines.append(QLineF(after_item->pos().x(), + before_item->pos().y(), + after_item->pos().x(), + after_item->pos().y())); } else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { // Draw a cubic bezier + + // Cubic beziers have two control points, so we can just use both + QPointF before_control_point = before_item->pos() + ScalePoint(before->bezier_control_out()); + QPointF after_control_point = after_item->pos() + ScalePoint(after->bezier_control_in()); + + QPainterPath path; + path.moveTo(before_item->pos()); + path.cubicTo(before_control_point, after_control_point, after_item->pos()); + painter->drawPath(path); + } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) { // Draw a quadratic bezier + + // Quadratic beziers have a single control point, we just have to determine which it is + QPointF key_anchor; + QPointF control_point; + + if (before->type() == NodeKeyframe::kBezier) { + key_anchor = before_item->pos(); + control_point = before->bezier_control_out(); + } else { + key_anchor = after_item->pos(); + control_point = after->bezier_control_in(); + } + + // Scale control point + control_point = key_anchor + ScalePoint(control_point); + + // Create the path from both keyframes + QPainterPath path; + path.moveTo(before_item->pos()); + path.quadTo(control_point, after_item->pos()); + painter->drawPath(path); + } else { // Linear to linear - keyframe_lines.append(QLineF(before_item->center_pos(), after_item->center_pos())); + keyframe_lines.append(QLineF(before_item->pos(), after_item->pos())); } } // Draw straight line leading from end keyframe - QPointF last_key_pos = item_map().value(keys.last())->center_pos(); + QPointF last_key_pos = item_map().value(keys.last())->pos(); keyframe_lines.append(QLineF(last_key_pos, QPointF(scene_top_right.x(), last_key_pos.y()))); painter->drawLines(keyframe_lines); } + + // Draw bezier control point lines + if (!bezier_control_points_.isEmpty()) { + painter->setPen(QPen(palette().text().color(), 1)); + + QVector bezier_lines; + foreach (BezierControlPointItem* item, bezier_control_points_) { + // All BezierControlPointItems should be children of a KeyframeViewItem + KeyframeViewItem* par = static_cast(item->parentItem()); + + bezier_lines.append(QLineF(par->pos(), par->pos() + item->pos())); + } + painter->drawLines(bezier_lines); + } } void CurveView::KeyframeAboutToBeRemoved(NodeKeyframe *key) @@ -133,6 +189,15 @@ void CurveView::KeyframeAboutToBeRemoved(NodeKeyframe *key) disconnect(key, &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged); } +void CurveView::ScaleChangedEvent(double scale) +{ + KeyframeViewBase::ScaleChangedEvent(scale); + + foreach (BezierControlPointItem* item, bezier_control_points_) { + item->SetXScale(scale); + } +} + QList CurveView::GetKeyframesSortedByTime() { QList sorted; @@ -169,6 +234,11 @@ void CurveView::SetItemYFromKeyframeValue(NodeKeyframe *key, KeyframeViewItem *i item->SetOverrideY(GetItemYFromKeyframeValue(key)); } +QPointF CurveView::ScalePoint(const QPointF &point) +{ + return QPointF(point.x() * scale_, point.y() * y_scale_); +} + void CurveView::KeyframeValueChanged() { NodeKeyframe* key = static_cast(sender()); @@ -177,6 +247,37 @@ void CurveView::KeyframeValueChanged() SetItemYFromKeyframeValue(key, item); } +void CurveView::SelectionChanged() +{ + // Clear current bezier handles + foreach (BezierControlPointItem* item, bezier_control_points_) { + delete item; + } + bezier_control_points_.clear(); + + QList selected = scene()->selectedItems(); + + foreach (QGraphicsItem* item, selected) { + KeyframeViewItem* this_item = static_cast(item); + + BezierControlPointItem* bezier_in_pt = new BezierControlPointItem(this_item->key(), NodeKeyframe::kInHandle, item); + bezier_in_pt->SetXScale(scale_); + bezier_control_points_.append(bezier_in_pt); + connect(bezier_in_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection); + + BezierControlPointItem* bezier_out_pt = new BezierControlPointItem(this_item->key(), NodeKeyframe::kOutHandle, item); + bezier_out_pt->SetXScale(scale_); + bezier_control_points_.append(bezier_out_pt); + connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection); + } +} + +void CurveView::BezierControlPointDestroyed() +{ + BezierControlPointItem* item = static_cast(sender()); + bezier_control_points_.removeOne(item); +} + void CurveView::AddKeyframe(NodeKeyframePtr key) { KeyframeViewItem* item = AddKeyframeInternal(key); diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index 7c8ae3722..84163b646 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -1,6 +1,7 @@ #ifndef CURVEVIEW_H #define CURVEVIEW_H +#include "beziercontrolpointitem.h" #include "node/keyframe.h" #include "widget/keyframeview/keyframeview.h" #include "widget/keyframeview/keyframeviewitem.h" @@ -10,6 +11,8 @@ class CurveView : public KeyframeViewBase public: CurveView(QWidget* parent = nullptr); + virtual ~CurveView() override; + virtual void Clear() override; void SetYScale(const double& y_scale); @@ -22,6 +25,8 @@ protected: virtual void KeyframeAboutToBeRemoved(NodeKeyframe *key) override; + virtual void ScaleChangedEvent(double scale) override; + private: QList GetKeyframesSortedByTime(); @@ -29,6 +34,8 @@ private: void SetItemYFromKeyframeValue(NodeKeyframe* key, KeyframeViewItem* item); + QPointF ScalePoint(const QPointF& point); + void AdjustLines(); int text_padding_; @@ -39,9 +46,15 @@ private: QList lines_; + QList bezier_control_points_; + private slots: void KeyframeValueChanged(); + void SelectionChanged(); + + void BezierControlPointDestroyed(); + }; #endif // CURVEVIEW_H diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index c169a8b04..c71c9fe6e 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -9,7 +9,6 @@ #include "common/timecodefunctions.h" #include "node/node.h" #include "widget/keyframeview/keyframeviewundo.h" -#include "widget/nodeparamview/nodeparamviewkeyframecontrol.h" CurveWidget::CurveWidget(QWidget *parent) : QWidget(parent), @@ -20,12 +19,9 @@ CurveWidget::CurveWidget(QWidget *parent) : QHBoxLayout* top_controls = new QHBoxLayout(); - NodeParamViewKeyframeControl* key_controls = new NodeParamViewKeyframeControl(false); - key_controls->SetEnableButtonVisible(false); - key_controls->SetPreviousButtonEnabled(false); - key_controls->SetToggleButtonEnabled(false); - key_controls->SetNextButtonEnabled(false); - top_controls->addWidget(key_controls); + key_control_ = new NodeParamViewKeyframeControl(false); + connect(key_control_, &NodeParamViewKeyframeControl::RequestSetTime, this, &CurveWidget::KeyControlRequestedTimeChanged); + top_controls->addWidget(key_control_); top_controls->addStretch(); @@ -83,7 +79,7 @@ CurveWidget::CurveWidget(QWidget *parent) : CurveWidget::~CurveWidget() { - // Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after the buttons have been destroyed + // Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after other memebers have been destroyed view_->Clear(); } @@ -105,6 +101,7 @@ void CurveWidget::SetInput(NodeInput *input) view_->Clear(); input_ = input; + key_control_->SetInput(input_); if (input_) { bridge_ = new NodeParamViewWidgetBridge(input_, this); @@ -195,6 +192,7 @@ void CurveWidget::UpdateBridgeTime(const int64_t ×tamp) rational time = Timecode::timestamp_to_time(timestamp, view_->timebase()); bridge_->SetTime(time); + key_control_->SetTime(time); } void CurveWidget::SelectionChanged() @@ -265,3 +263,12 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked) Core::instance()->undo_stack()->push(command); } + +void CurveWidget::KeyControlRequestedTimeChanged(const rational &time) +{ + int64_t timestamp = Timecode::time_to_timestamp(time, view_->timebase()); + + SetTime(timestamp); + + emit TimeChanged(timestamp); +} diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index be121f8a2..c9e3db4f1 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -7,6 +7,7 @@ #include "curveview.h" #include "node/input.h" +#include "widget/nodeparamview/nodeparamviewkeyframecontrol.h" #include "widget/nodeparamview/nodeparamviewwidgetbridge.h" #include "widget/timeruler/timeruler.h" @@ -60,6 +61,8 @@ private: NodeParamViewWidgetBridge* bridge_; + NodeParamViewKeyframeControl* key_control_; + private slots: void UpdateBridgeTime(const int64_t& timestamp); @@ -67,6 +70,8 @@ private slots: void KeyframeTypeButtonTriggered(bool checked); + void KeyControlRequestedTimeChanged(const rational& time); + }; #endif // CURVEWIDGET_H diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index e8417b94b..c1458ddb6 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -9,7 +9,8 @@ #include "widget/nodeparamview/nodeparamviewundo.h" KeyframeViewBase::KeyframeViewBase(QWidget *parent) : - TimelineViewBase(parent) + TimelineViewBase(parent), + dragging_bezier_point_(nullptr) { setDragMode(RubberBandDrag); setContextMenuPolicy(Qt::CustomContextMenu); @@ -62,16 +63,25 @@ void KeyframeViewBase::mousePressEvent(QMouseEvent *event) QGraphicsItem* item_under_cursor = itemAt(event->pos()); if (item_under_cursor) { - QList selected_items = scene()->selectedItems(); drag_start_ = event->pos(); - selected_keys_.resize(selected_items.size()); + // Determine what type of item is under the cursor + dragging_bezier_point_ = dynamic_cast(item_under_cursor); - for (int i=0;i(selected_items.at(i)); + if (dragging_bezier_point_) { + dragging_bezier_point_start_ = dragging_bezier_point_->GetCorrespondingKeyframeHandle(); + dragging_bezier_point_opposing_start_ = dragging_bezier_point_->key()->bezier_control(NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode())); + } else { + QList selected_items = scene()->selectedItems(); - selected_keys_.replace(i, {key, key->x(), key->key()->time()}); + selected_keys_.resize(selected_items.size()); + + for (int i=0;i(selected_items.at(i)); + + selected_keys_.replace(i, {key, key->x(), key->key()->time()}); + } } } } @@ -87,13 +97,19 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) if (event->buttons() & Qt::LeftButton) { QGraphicsView::mouseMoveEvent(event); - if (active_tool_ == Tool::kPointer && !selected_keys_.isEmpty()) { - int x_diff = event->pos().x() - drag_start_.x(); - double x_diff_scaled = static_cast(x_diff) / scale_; + if (active_tool_ == Tool::kPointer) { + // Calculate cursor difference and scale it + QPointF mouse_diff_scaled = GetScaledCursorPos(event->pos() - drag_start_); - foreach (const KeyframeItemAndTime& keypair, selected_keys_) { - // FIXME: Find some way to do single frame updates as the NodeParamViewWidgetBridge does? - keypair.key->key()->set_time(CalculateNewTimeFromScreen(keypair.time, x_diff_scaled)); + if (dragging_bezier_point_) { + ProcessBezierDrag(mouse_diff_scaled, + !(event->modifiers() & Qt::ControlModifier), + false); + } else if (!selected_keys_.isEmpty()) { + foreach (const KeyframeItemAndTime& keypair, selected_keys_) { + // FIXME: Find some way to do single frame updates as the NodeParamViewWidgetBridge does? + keypair.key->key()->set_time(CalculateNewTimeFromScreen(keypair.time, mouse_diff_scaled.x())); + } } } } @@ -108,29 +124,38 @@ void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) if (event->button() == Qt::LeftButton) { QGraphicsView::mouseReleaseEvent(event); - if (active_tool_ == Tool::kPointer && !selected_keys_.isEmpty()) { - QUndoCommand* command = new QUndoCommand(); + if (active_tool_ == Tool::kPointer) { + QPoint mouse_diff = event->pos() - drag_start_; + QPointF mouse_diff_scaled = GetScaledCursorPos(mouse_diff); - // Calculate X movement and scaling to timeline time - int x_diff = event->pos().x() - drag_start_.x(); - double x_diff_scaled = static_cast(x_diff) / scale_; + if (!mouse_diff.isNull()) { + if (dragging_bezier_point_) { + ProcessBezierDrag(mouse_diff_scaled, + !(event->modifiers() & Qt::ControlModifier), + true); - foreach (const KeyframeItemAndTime& keypair, selected_keys_) { - KeyframeViewItem* item = keypair.key; + dragging_bezier_point_ = nullptr; + } else if (!selected_keys_.isEmpty()) { + QUndoCommand* command = new QUndoCommand(); - // Calculate the new time for this keyframe - rational new_time = CalculateNewTimeFromScreen(keypair.time, x_diff_scaled); + foreach (const KeyframeItemAndTime& keypair, selected_keys_) { + KeyframeViewItem* item = keypair.key; - // Commit movement - new NodeParamSetKeyframeTimeCommand(item->key(), - new_time, - keypair.time, - command); + // Calculate the new time for this keyframe + rational new_time = CalculateNewTimeFromScreen(keypair.time, mouse_diff_scaled.x()); + + // Commit movement + new NodeParamSetKeyframeTimeCommand(item->key(), + new_time, + keypair.time, + command); + } + + Core::instance()->undo_stack()->push(command); + + selected_keys_.clear(); + } } - - Core::instance()->undo_stack()->push(command); - - selected_keys_.clear(); } } } @@ -158,6 +183,77 @@ rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, return rational::fromDouble(old_time.toDouble() + cursor_diff); } +QPointF KeyframeViewBase::GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, const QPointF &start_point, const QPointF &scaled_cursor_diff) +{ + QPointF new_bezier_pos = start_point; + + new_bezier_pos += scaled_cursor_diff; + + // LIMIT bezier handles from overlapping each other + if (mode == NodeKeyframe::kInHandle) { + if (new_bezier_pos.x() > 0) { + new_bezier_pos.setX(0); + } + } else { + if (new_bezier_pos.x() < 0) { + new_bezier_pos.setX(0); + } + } + + return new_bezier_pos; +} + +void KeyframeViewBase::ProcessBezierDrag(const QPointF& mouse_diff_scaled, bool include_opposing, bool undoable) +{ + QPointF new_bezier_pos = GenerateBezierControlPosition(dragging_bezier_point_->mode(), + dragging_bezier_point_start_, + mouse_diff_scaled); + + // If the user is NOT holding control, we set the other handle to the exact negative of this handle + QPointF new_opposing_pos; + NodeKeyframe::BezierType opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode()); + + if (include_opposing) { + new_opposing_pos = GenerateBezierControlPosition(opposing_type, + dragging_bezier_point_opposing_start_, + -mouse_diff_scaled); + } else { + new_opposing_pos = dragging_bezier_point_opposing_start_; + } + + dragging_bezier_point_->key()->set_bezier_control(dragging_bezier_point_->mode(), + new_bezier_pos); + + dragging_bezier_point_->key()->set_bezier_control(opposing_type, + new_opposing_pos); + + if (undoable) { + QUndoCommand* command = new QUndoCommand(); + + new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(), + dragging_bezier_point_->mode(), + new_bezier_pos, + dragging_bezier_point_start_, + command); + + if (include_opposing) { + new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(), + opposing_type, + new_opposing_pos, + dragging_bezier_point_opposing_start_, + command); + } + + Core::instance()->undo_stack()->push(command); + } +} + +QPointF KeyframeViewBase::GetScaledCursorPos(const QPoint &cursor_pos) +{ + return QPointF (static_cast(cursor_pos.x()) / scale_, + cursor_pos.y());; +} + void KeyframeViewBase::ShowContextMenu() { Menu m; diff --git a/app/widget/keyframeview/keyframeviewbase.h b/app/widget/keyframeview/keyframeviewbase.h index 5af1eba12..1df11f569 100644 --- a/app/widget/keyframeview/keyframeviewbase.h +++ b/app/widget/keyframeview/keyframeviewbase.h @@ -4,6 +4,7 @@ #include "core.h" #include "keyframeviewitem.h" #include "node/keyframe.h" +#include "widget/curvewidget/beziercontrolpointitem.h" #include "widget/timelinewidget/view/timelineviewbase.h" class KeyframeViewBase : public TimelineViewBase @@ -33,6 +34,14 @@ protected: private: rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff); + static QPointF GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, + const QPointF& start_point, + const QPointF& scaled_cursor_diff); + + void ProcessBezierDrag(const QPointF& mouse_diff_scaled, bool include_opposing, bool undoable); + + QPointF GetScaledCursorPos(const QPoint& cursor_pos); + struct KeyframeItemAndTime { KeyframeViewItem* key; qreal item_x; @@ -45,6 +54,10 @@ private: QPoint drag_start_; + BezierControlPointItem* dragging_bezier_point_; + QPointF dragging_bezier_point_start_; + QPointF dragging_bezier_point_opposing_start_; + QVector selected_keys_; private slots: diff --git a/app/widget/keyframeview/keyframeviewitem.cpp b/app/widget/keyframeview/keyframeviewitem.cpp index de8da1ef4..94500acd0 100644 --- a/app/widget/keyframeview/keyframeviewitem.cpp +++ b/app/widget/keyframeview/keyframeviewitem.cpp @@ -13,13 +13,14 @@ KeyframeViewItem::KeyframeViewItem(NodeKeyframePtr key, QGraphicsItem *parent) : scale_(1.0), vert_center_(0) { - keyframe_size_ = QFontMetricsWidth(qApp->fontMetrics(), "Oi"); setFlag(QGraphicsItem::ItemIsSelectable); connect(key.get(), &NodeKeyframe::TimeChanged, this, &KeyframeViewItem::UpdatePos); connect(key.get(), &NodeKeyframe::TypeChanged, this, &KeyframeViewItem::Redraw); - setRect(0, 0, keyframe_size_, keyframe_size_); + int keyframe_size = QFontMetricsWidth(qApp->fontMetrics(), "Oi"); + int half_sz = keyframe_size/2; + setRect(-half_sz, -half_sz, keyframe_size, keyframe_size); UpdatePos(); } @@ -41,11 +42,6 @@ NodeKeyframePtr KeyframeViewItem::key() const return key_; } -QPointF KeyframeViewItem::center_pos() const -{ - return pos() + rect().center(); -} - void KeyframeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { painter->setPen(Qt::black); @@ -82,7 +78,7 @@ void KeyframeViewItem::UpdatePos() { double x_center = key_->time().toDouble() * scale_; - setPos(x_center - keyframe_size_/2, vert_center_ - keyframe_size_/2); + setPos(x_center, vert_center_); } void KeyframeViewItem::Redraw() diff --git a/app/widget/keyframeview/keyframeviewitem.h b/app/widget/keyframeview/keyframeviewitem.h index 7508bae07..49017ecc2 100644 --- a/app/widget/keyframeview/keyframeviewitem.h +++ b/app/widget/keyframeview/keyframeviewitem.h @@ -17,8 +17,6 @@ public: NodeKeyframePtr key() const; - QPointF center_pos() const; - protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; @@ -29,8 +27,6 @@ private: qreal vert_center_; - int keyframe_size_; - private slots: void UpdatePos(); diff --git a/app/widget/keyframeview/keyframeviewundo.cpp b/app/widget/keyframeview/keyframeviewundo.cpp index d87606f57..858979357 100644 --- a/app/widget/keyframeview/keyframeviewundo.cpp +++ b/app/widget/keyframeview/keyframeviewundo.cpp @@ -17,3 +17,31 @@ void KeyframeSetTypeCommand::undo() { key_->set_type(old_type_); } + +KeyframeSetBezierControlPoint::KeyframeSetBezierControlPoint(NodeKeyframePtr key, NodeKeyframe::BezierType mode, const QPointF& point, QUndoCommand *parent) : + QUndoCommand(parent), + key_(key), + mode_(mode), + old_point_(key->bezier_control(mode_)), + new_point_(point) +{ +} + +KeyframeSetBezierControlPoint::KeyframeSetBezierControlPoint(NodeKeyframePtr key, NodeKeyframe::BezierType mode, const QPointF &new_point, const QPointF &old_point, QUndoCommand *parent) : + QUndoCommand(parent), + key_(key), + mode_(mode), + old_point_(old_point), + new_point_(new_point) +{ +} + +void KeyframeSetBezierControlPoint::redo() +{ + key_->set_bezier_control(mode_, new_point_); +} + +void KeyframeSetBezierControlPoint::undo() +{ + key_->set_bezier_control(mode_, old_point_); +} diff --git a/app/widget/keyframeview/keyframeviewundo.h b/app/widget/keyframeview/keyframeviewundo.h index 2c1a90de7..c58ce00eb 100644 --- a/app/widget/keyframeview/keyframeviewundo.h +++ b/app/widget/keyframeview/keyframeviewundo.h @@ -21,4 +21,23 @@ private: }; +class KeyframeSetBezierControlPoint : public QUndoCommand { +public: + KeyframeSetBezierControlPoint(NodeKeyframePtr key, NodeKeyframe::BezierType mode, const QPointF& point, QUndoCommand* parent = nullptr); + KeyframeSetBezierControlPoint(NodeKeyframePtr key, NodeKeyframe::BezierType mode, const QPointF& new_point, const QPointF& old_point, QUndoCommand* parent = nullptr); + + virtual void redo() override; + virtual void undo() override; + +private: + NodeKeyframePtr key_; + + NodeKeyframe::BezierType mode_; + + QPointF old_point_; + + QPointF new_point_; + +}; + #endif // KEYFRAMEVIEWUNDO_H diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 64eaea125..be61ffc41 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include "core.h" @@ -84,7 +83,7 @@ void NodeParamViewItem::SetTime(const rational &time) } foreach (NodeParamViewKeyframeControl* key_control, key_control_list_) { - UpdateKeyframeControl(key_control); + key_control->SetTime(time_); } } @@ -121,9 +120,6 @@ void NodeParamViewItem::InputAddedKeyframeInternal(NodeInput *input, NodeKeyfram // Find global position lbl_center = lbl->mapToGlobal(lbl_center); - // Update keyframe control widget - UpdateKeyframeControl(KeyframeControlFromInput(input)); - emit KeyframeAdded(keyframe, lbl_center.y()); } @@ -163,10 +159,7 @@ void NodeParamViewItem::SetupUI() NodeParamViewKeyframeControl* key_control = new NodeParamViewKeyframeControl(); key_control->SetInput(input); content_layout_->addWidget(key_control, row_count, control_column); - connect(key_control, &NodeParamViewKeyframeControl::KeyframeEnableChanged, this, &NodeParamViewItem::UserChangedKeyframeEnable); - connect(key_control, &NodeParamViewKeyframeControl::GoToPreviousKey, this, &NodeParamViewItem::GoToPreviousKey); - connect(key_control, &NodeParamViewKeyframeControl::GoToNextKey, this, &NodeParamViewItem::GoToNextKey); - connect(key_control, &NodeParamViewKeyframeControl::KeyframeToggled, this, &NodeParamViewItem::UserToggledKeyframe); + connect(key_control, &NodeParamViewKeyframeControl::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); key_control_list_.append(key_control); connect(input, &NodeInput::KeyframeEnableChanged, this, &NodeParamViewItem::InputKeyframeEnableChanged); @@ -199,16 +192,6 @@ void NodeParamViewItem::Retranslate() } } -void NodeParamViewItem::UpdateKeyframeControl(NodeParamViewKeyframeControl *key_control) -{ - NodeInput* input = key_control->GetConnectedInput(); - - // Update UI based on time - key_control->SetPreviousButtonEnabled(!input->keyframes().isEmpty() && time_ > input->keyframes().first()->time()); - key_control->SetNextButtonEnabled(!input->keyframes().isEmpty() && time_ < input->keyframes().last()->time()); - key_control->SetToggleButtonChecked(input->has_keyframe_at_time(time_)); -} - NodeParamViewKeyframeControl *NodeParamViewItem::KeyframeControlFromInput(NodeInput *input) const { foreach (NodeParamViewKeyframeControl* key_control, key_control_list_) { @@ -233,83 +216,6 @@ void NodeParamViewItem::SetExpanded(bool e) } } -void NodeParamViewItem::UserChangedKeyframeEnable(bool e) -{ - NodeParamViewKeyframeControl* control = static_cast(sender()); - NodeInput* input = control->GetConnectedInput(); - - if (e == input->is_keyframing()) { - // No-op - return; - } - - QUndoCommand* command = new QUndoCommand(); - - if (e) { - // Enable keyframing - new NodeParamSetKeyframingCommand(input, true, command); - - // NodeInputs already have one keyframe by default, we move it to the current time here - NodeKeyframePtr key = std::make_shared(time_, input->get_standard_value(), NodeKeyframe::kDefaultType); - new NodeParamInsertKeyframeCommand(input, key, command); - } else { - // Confirm the user wants to clear all keyframes - if (QMessageBox::warning(this, - tr("Warning"), - tr("Are you sure you want to disable keyframing on this value? This will clear all existing keyframes."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - - // Store value at this time, we'll set this as the persistent value later - QVariant stored_val = input->get_value_at_time(time_); - - // Delete all keyframes - for (int i=input->keyframes().size()-1;i>=0;i--) { - new NodeParamRemoveKeyframeCommand(input, input->keyframes().at(i), command); - } - - // Update standard value - new NodeParamSetStandardValueCommand(input, stored_val, command); - - // Disable keyframing - new NodeParamSetKeyframingCommand(input, false, command); - - } else { - // Disable action has effectively been ignored - control->SetKeyframeEnabled(true); - } - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); -} - -void NodeParamViewItem::UserToggledKeyframe(bool e) -{ - NodeParamViewKeyframeControl* control = static_cast(sender()); - NodeInput* input = control->GetConnectedInput(); - NodeKeyframePtr key = input->get_keyframe_at_time(time_); - - QUndoCommand* command = new QUndoCommand(); - - if (e && !key) { - // Add a keyframe here - key = std::make_shared(time_, - input->get_value_at_time(time_), - input->get_best_keyframe_type_for_time(time_)); - - new NodeParamInsertKeyframeCommand(input, key, command); - } else if (!e && key) { - // Remove a keyframe here - new NodeParamRemoveKeyframeCommand(input, key, command); - - // If this was the last keyframe, we'll set the standard value to the value at this time too - if (input->keyframes().size() == 1) { - new NodeParamSetStandardValueCommand(input, key->value(), command); - } - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); -} - void NodeParamViewItem::InputKeyframeEnableChanged(bool e) { NodeInput* input = static_cast(sender()); @@ -333,38 +239,6 @@ void NodeParamViewItem::InputAddedKeyframe(NodeKeyframePtr key) InputAddedKeyframeInternal(input, key); } -void NodeParamViewItem::GoToPreviousKey() -{ - NodeParamViewKeyframeControl* key_control = static_cast(sender()); - NodeInput* input = key_control->GetConnectedInput(); - - for (int i=input->keyframes().size()-1;i>=0;i--) { - // Find closest keyframe that is before this time - const rational& this_key_time = input->keyframes().at(i)->time(); - - if (this_key_time < time_) { - emit RequestSetTime(this_key_time); - break; - } - } -} - -void NodeParamViewItem::GoToNextKey() -{ - NodeParamViewKeyframeControl* key_control = static_cast(sender()); - NodeInput* input = key_control->GetConnectedInput(); - - for (int i=0;ikeyframes().size();i++) { - // Find closest keyframe that is before this time - const rational& this_key_time = input->keyframes().at(i)->time(); - - if (this_key_time > time_) { - emit RequestSetTime(this_key_time); - break; - } - } -} - void NodeParamViewItem::LabelClicked() { ClickableLabel* lbl = static_cast(sender()); diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 0177fa8bc..3bd027908 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -70,8 +70,6 @@ private: void Retranslate(); - void UpdateKeyframeControl(NodeParamViewKeyframeControl* key_control); - NodeParamViewKeyframeControl* KeyframeControlFromInput(NodeInput* input) const; bool expanded_; @@ -101,18 +99,10 @@ private: private slots: void SetExpanded(bool e); - void UserChangedKeyframeEnable(bool e); - - void UserToggledKeyframe(bool e); - void InputKeyframeEnableChanged(bool e); void InputAddedKeyframe(NodeKeyframePtr key); - void GoToPreviousKey(); - - void GoToNextKey(); - void LabelClicked(); }; diff --git a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp index 66d9e26d4..79331f851 100644 --- a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp +++ b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp @@ -1,7 +1,10 @@ #include "nodeparamviewkeyframecontrol.h" #include +#include +#include "core.h" +#include "nodeparamviewundo.h" #include "ui/icons/icons.h" NodeParamViewKeyframeControl::NodeParamViewKeyframeControl(bool right_align, QWidget *parent) : @@ -37,9 +40,13 @@ NodeParamViewKeyframeControl::NodeParamViewKeyframeControl(bool right_align, QWi connect(prev_key_btn_, &QPushButton::clicked, this, &NodeParamViewKeyframeControl::GoToPreviousKey); connect(next_key_btn_, &QPushButton::clicked, this, &NodeParamViewKeyframeControl::GoToNextKey); - connect(toggle_key_btn_, &QPushButton::toggled, this, &NodeParamViewKeyframeControl::KeyframeToggled); + connect(toggle_key_btn_, &QPushButton::clicked, this, &NodeParamViewKeyframeControl::ToggleKeyframe); connect(enable_key_btn_, &QPushButton::toggled, this, &NodeParamViewKeyframeControl::ShowButtonsFromKeyframeEnable); - connect(enable_key_btn_, &QPushButton::toggled, this, &NodeParamViewKeyframeControl::KeyframeEnableChanged); + connect(enable_key_btn_, &QPushButton::clicked, this, &NodeParamViewKeyframeControl::KeyframeEnableChanged); + + // Set defaults + SetInput(nullptr); + ShowButtonsFromKeyframeEnable(false); } NodeInput *NodeParamViewKeyframeControl::GetConnectedInput() const @@ -47,60 +54,35 @@ NodeInput *NodeParamViewKeyframeControl::GetConnectedInput() const return input_; } -void NodeParamViewKeyframeControl::SetPreviousButtonEnabled(bool enabled) -{ - prev_key_btn_->setEnabled(enabled); -} - -void NodeParamViewKeyframeControl::SetNextButtonEnabled(bool enabled) -{ - next_key_btn_->setEnabled(enabled); -} - -void NodeParamViewKeyframeControl::SetToggleButtonEnabled(bool enable) -{ - toggle_key_btn_->setEnabled(enable); -} - -void NodeParamViewKeyframeControl::SetToggleButtonChecked(bool checked) -{ - // Suppress KeyframeToggled() signal from this object - blockSignals(true); - - toggle_key_btn_->setChecked(checked); - - blockSignals(false); -} - -void NodeParamViewKeyframeControl::SetEnableButtonVisible(bool visible) -{ - enable_key_btn_->setVisible(visible); -} - void NodeParamViewKeyframeControl::SetInput(NodeInput *input) { if (input_ != nullptr) { - disconnect(input_, &NodeInput::KeyframeEnableChanged, this, &NodeParamViewKeyframeControl::SetKeyframeEnabled); + disconnect(input_, &NodeInput::KeyframeEnableChanged, enable_key_btn_, &QPushButton::setChecked); + disconnect(input_, &NodeInput::KeyframeAdded, this, &NodeParamViewKeyframeControl::UpdateState); + disconnect(input_, &NodeInput::KeyframeRemoved, this, &NodeParamViewKeyframeControl::UpdateState); } input_ = input; + SetButtonsEnabled(input_); + + // Pick up keyframing value + enable_key_btn_->setChecked(input_ && input_->is_keyframing()); + + // Update buttons + UpdateState(); if (input_ != nullptr) { - connect(input_, &NodeInput::KeyframeEnableChanged, this, &NodeParamViewKeyframeControl::SetKeyframeEnabled); - - // Pick up keyframing value - ShowButtonsFromKeyframeEnable(input_->is_keyframing()); + connect(input_, &NodeInput::KeyframeEnableChanged, enable_key_btn_, &QPushButton::setChecked); + connect(input_, &NodeInput::KeyframeAdded, this, &NodeParamViewKeyframeControl::UpdateState); + connect(input_, &NodeInput::KeyframeRemoved, this, &NodeParamViewKeyframeControl::UpdateState); } } -void NodeParamViewKeyframeControl::SetKeyframeEnabled(bool e) +void NodeParamViewKeyframeControl::SetTime(const rational &time) { - // Suppress KeyframeEnableChanged() signal from this object - blockSignals(true); + time_ = time; - enable_key_btn_->setChecked(e); - - blockSignals(false); + UpdateState(); } QPushButton *NodeParamViewKeyframeControl::CreateNewToolButton(const QIcon& icon) const @@ -112,9 +94,126 @@ QPushButton *NodeParamViewKeyframeControl::CreateNewToolButton(const QIcon& icon return btn; } +void NodeParamViewKeyframeControl::SetButtonsEnabled(bool e) +{ + prev_key_btn_->setEnabled(e); + toggle_key_btn_->setEnabled(e); + next_key_btn_->setEnabled(e); + enable_key_btn_->setEnabled(e); +} + void NodeParamViewKeyframeControl::ShowButtonsFromKeyframeEnable(bool e) { prev_key_btn_->setVisible(e); toggle_key_btn_->setVisible(e); next_key_btn_->setVisible(e); } + +void NodeParamViewKeyframeControl::ToggleKeyframe(bool e) +{ + NodeKeyframePtr key = input_->get_keyframe_at_time(time_); + + QUndoCommand* command = new QUndoCommand(); + + if (e && !key) { + // Add a keyframe here + key = NodeKeyframe::Create(time_, + input_->get_value_at_time(time_), + input_->get_best_keyframe_type_for_time(time_)); + + new NodeParamInsertKeyframeCommand(input_, key, command); + } else if (!e && key) { + // Remove a keyframe here + new NodeParamRemoveKeyframeCommand(input_, key, command); + + // If this was the last keyframe, we'll set the standard value to the value at this time too + if (input_->keyframes().size() == 1) { + new NodeParamSetStandardValueCommand(input_, key->value(), command); + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); +} + +void NodeParamViewKeyframeControl::UpdateState() +{ + if (!input_) { + return; + } + + prev_key_btn_->setEnabled(!input_->keyframes().isEmpty() && time_ > input_->keyframes().first()->time()); + next_key_btn_->setEnabled(!input_->keyframes().isEmpty() && time_ < input_->keyframes().last()->time()); + toggle_key_btn_->setChecked(input_->has_keyframe_at_time(time_)); +} + +void NodeParamViewKeyframeControl::GoToPreviousKey() +{ + for (int i=input_->keyframes().size()-1;i>=0;i--) { + // Find closest keyframe that is before this time + const rational& this_key_time = input_->keyframes().at(i)->time(); + + if (this_key_time < time_) { + emit RequestSetTime(this_key_time); + break; + } + } +} + +void NodeParamViewKeyframeControl::GoToNextKey() +{ + for (int i=0;ikeyframes().size();i++) { + // Find closest keyframe that is before this time + const rational& this_key_time = input_->keyframes().at(i)->time(); + + if (this_key_time > time_) { + emit RequestSetTime(this_key_time); + break; + } + } +} + +void NodeParamViewKeyframeControl::KeyframeEnableChanged(bool e) +{ + if (e == input_->is_keyframing()) { + // No-op + return; + } + + QUndoCommand* command = new QUndoCommand(); + + if (e) { + // Enable keyframing + new NodeParamSetKeyframingCommand(input_, true, command); + + // NodeInputs already have one keyframe by default, we move it to the current time here + NodeKeyframePtr key = NodeKeyframe::Create(time_, input_->get_standard_value(), NodeKeyframe::kDefaultType); + new NodeParamInsertKeyframeCommand(input_, key, command); + } else { + // Confirm the user wants to clear all keyframes + if (QMessageBox::warning(this, + tr("Warning"), + tr("Are you sure you want to disable keyframing on this value? This will clear all existing keyframes."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + + // Store value at this time, we'll set this as the persistent value later + QVariant stored_val = input_->get_value_at_time(time_); + + // Delete all keyframes + for (int i=input_->keyframes().size()-1;i>=0;i--) { + new NodeParamRemoveKeyframeCommand(input_, input_->keyframes().at(i), command); + } + + // Update standard value + new NodeParamSetStandardValueCommand(input_, stored_val, command); + + // Disable keyframing + new NodeParamSetKeyframingCommand(input_, false, command); + + } else { + // Disable action has effectively been ignored + enable_key_btn_->setChecked(true); + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); +} diff --git a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h index 028925bff..44f0c3ea0 100644 --- a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h +++ b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h @@ -14,28 +14,18 @@ public: NodeInput* GetConnectedInput() const; - void SetPreviousButtonEnabled(bool enabled); - void SetNextButtonEnabled(bool enabled); - void SetToggleButtonEnabled(bool enable); - void SetToggleButtonChecked(bool checked); - void SetEnableButtonVisible(bool visible); - void SetInput(NodeInput* input); -public slots: - void SetKeyframeEnabled(bool e); + void SetTime(const rational& time); signals: - void KeyframeEnableChanged(bool); - - void GoToPreviousKey(); - void GoToNextKey(); - - void KeyframeToggled(bool); + void RequestSetTime(const rational& time); private: QPushButton* CreateNewToolButton(const QIcon &icon) const; + void SetButtonsEnabled(bool e); + QPushButton* prev_key_btn_; QPushButton* toggle_key_btn_; QPushButton* next_key_btn_; @@ -43,8 +33,21 @@ private: NodeInput* input_; + rational time_; + private slots: void ShowButtonsFromKeyframeEnable(bool e); + + void ToggleKeyframe(bool e); + + void UpdateState(); + + void GoToPreviousKey(); + + void GoToNextKey(); + + void KeyframeEnableChanged(bool e); + }; #endif // NODEPARAMVIEWKEYFRAMECONTROL_H diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index af63ab0d1..c563f773a 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -262,9 +262,9 @@ void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value) new NodeParamSetKeyframeValueCommand(existing_key, value, command); } else { // No existing key, create a new one - NodeKeyframePtr new_key = std::make_shared(time_, - value, - input_->get_best_keyframe_type_for_time(time_)); + NodeKeyframePtr new_key = NodeKeyframe::Create(time_, + value, + input_->get_best_keyframe_type_for_time(time_)); new NodeParamInsertKeyframeCommand(input_, new_key, command); } @@ -295,9 +295,9 @@ void NodeParamViewWidgetBridge::ProcessSlider(SliderBase *slider, const QVariant drag_created_keyframe_ = !dragging_keyframe_; if (drag_created_keyframe_) { - dragging_keyframe_ = std::make_shared(time_, - value, - input_->get_best_keyframe_type_for_time(time_)); + dragging_keyframe_ = NodeKeyframe::Create(time_, + value, + input_->get_best_keyframe_type_for_time(time_)); input_->insert_keyframe(dragging_keyframe_); diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index 4de889921..b6f0692a1 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -30,6 +30,7 @@ #include "ui/style/style.h" #include "undo/undostack.h" #include "widget/menu/menushared.h" +#include "mainwindow.h" MainMenu::MainMenu(QMainWindow *parent) : QMenuBar(parent) @@ -207,7 +208,7 @@ MainMenu::MainMenu(QMainWindow *parent) : window_lock_layout_item_ = window_menu_->AddItem("lockpanels", PanelManager::instance(), SLOT(SetPanelsLocked(bool))); window_lock_layout_item_->setCheckable(true); window_menu_->addSeparator(); - window_reset_layout_item_ = window_menu_->AddItem("resetdefaultlayout", nullptr, nullptr); + window_reset_layout_item_ = window_menu_->AddItem("resetdefaultlayout", Core::instance()->main_window(), SLOT(SetDefaultLayout())); // // TOOLS MENU diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 7743eaba2..c8864bf8a 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -48,6 +48,8 @@ public slots: void SetFullscreen(bool fullscreen); void ToggleMaximizedPanel(); + void SetDefaultLayout(); + protected: virtual void closeEvent(QCloseEvent* e) override; @@ -65,9 +67,6 @@ private: TaskManagerPanel* task_man_panel_; CurvePanel* curve_panel_; -private slots: - void SetDefaultLayout(); - }; #endif