#include "keyframeviewbase.h" #include #include #include "dialog/keyframeproperties/keyframeproperties.h" #include "keyframeviewundo.h" #include "node/node.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" #include "widget/nodeparamview/nodeparamviewundo.h" KeyframeViewBase::KeyframeViewBase(QWidget *parent) : TimelineViewBase(parent), y_scale_(1.0), dragging_bezier_point_(nullptr), y_axis_enabled_(false) { SetDefaultDragMode(RubberBandDrag); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &KeyframeViewBase::customContextMenuRequested, this, &KeyframeViewBase::ShowContextMenu); } void KeyframeViewBase::Clear() { QMap::iterator iterator; for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { delete iterator.value(); } item_map_.clear(); } const double &KeyframeViewBase::GetYScale() const { return y_scale_; } void KeyframeViewBase::SetYScale(const double &y_scale) { y_scale_ = y_scale; if (y_axis_enabled_) { VerticalScaleChangedEvent(y_scale_); viewport()->update(); } } void KeyframeViewBase::RemoveKeyframe(NodeKeyframePtr key) { KeyframeAboutToBeRemoved(key.get()); delete item_map_.take(key.get()); } KeyframeViewItem *KeyframeViewBase::AddKeyframeInternal(NodeKeyframePtr key) { KeyframeViewItem* item = new KeyframeViewItem(key); item->SetScale(GetScale()); item_map_.insert(key.get(), item); scene()->addItem(item); return item; } void KeyframeViewBase::mousePressEvent(QMouseEvent *event) { if (HandPress(event) || PlayheadPress(event)) { return; } active_tool_ = Core::instance()->tool(); if (event->button() == Qt::LeftButton) { QGraphicsView::mousePressEvent(event); if (active_tool_ == Tool::kPointer) { QGraphicsItem* item_under_cursor = itemAt(event->pos()); if (item_under_cursor) { drag_start_ = event->pos(); // Determine what type of item is under the cursor dragging_bezier_point_ = dynamic_cast(item_under_cursor); 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_.resize(selected_items.size()); for (int i=0;i(selected_items.at(i)); selected_keys_.replace(i, {key, key->x(), key->key()->time(), key->key()->value().toDouble()}); } } } } } } void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) { if (HandMove(event) || PlayheadMove(event)) { return; } if (event->buttons() & Qt::LeftButton) { QGraphicsView::mouseMoveEvent(event); if (active_tool_ == Tool::kPointer) { // Calculate cursor difference and scale it QPointF mouse_diff_scaled = GetScaledCursorPos(event->pos() - drag_start_); if (dragging_bezier_point_) { ProcessBezierDrag(mouse_diff_scaled, !(event->modifiers() & Qt::ControlModifier), false); } else if (!selected_keys_.isEmpty()) { foreach (const KeyframeItemAndTime& keypair, selected_keys_) { NodeInput* input_parent = keypair.key->key()->parent(); input_parent->blockSignals(true); keypair.key->key()->set_time(CalculateNewTimeFromScreen(keypair.time, mouse_diff_scaled.x())); if (y_axis_enabled_) { keypair.key->key()->set_value(keypair.value - mouse_diff_scaled.y()); } // We emit a custom value changed signal while the keyframe is being dragged so only the currently viewed // frame gets rendered in this time input_parent->blockSignals(false); input_parent->parentNode()->InvalidateVisible(input_parent); } } } } } void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) { if (HandRelease(event) || PlayheadRelease(event)) { return; } if (event->button() == Qt::LeftButton) { QGraphicsView::mouseReleaseEvent(event); if (active_tool_ == Tool::kPointer) { QPoint mouse_diff = event->pos() - drag_start_; QPointF mouse_diff_scaled = GetScaledCursorPos(mouse_diff); if (!mouse_diff.isNull()) { if (dragging_bezier_point_) { ProcessBezierDrag(mouse_diff_scaled, !(event->modifiers() & Qt::ControlModifier), true); dragging_bezier_point_ = nullptr; } else if (!selected_keys_.isEmpty()) { QUndoCommand* command = new QUndoCommand(); foreach (const KeyframeItemAndTime& keypair, selected_keys_) { KeyframeViewItem* item = keypair.key; keypair.key->key()->parent()->blockSignals(true); // Calculate the new time for this keyframe rational new_time = CalculateNewTimeFromScreen(keypair.time, mouse_diff_scaled.x()); // Commit movement // Since we overrode the cache signalling while dragging, we simulate here precisely the change that // occurred by first setting the keyframe to its original position, and then letting the input handle // the signalling once the undo command is pushed. item->key()->set_time(keypair.time); new NodeParamSetKeyframeTimeCommand(item->key(), new_time, keypair.time, command); // Commit value if we're setting a value if (y_axis_enabled_) { item->key()->set_value(keypair.value); new NodeParamSetKeyframeValueCommand(item->key(), keypair.value - mouse_diff_scaled.y(), keypair.value, command); } keypair.key->key()->parent()->blockSignals(false); } Core::instance()->undo_stack()->push(command); } } selected_keys_.clear(); } } } void KeyframeViewBase::ScaleChangedEvent(const double &scale) { TimelineViewBase::ScaleChangedEvent(scale); QMap::const_iterator iterator; for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { iterator.value()->SetScale(scale); } } void KeyframeViewBase::VerticalScaleChangedEvent(double) { } const QMap &KeyframeViewBase::item_map() const { return item_map_; } void KeyframeViewBase::KeyframeAboutToBeRemoved(NodeKeyframe *) { } void KeyframeViewBase::SetYAxisEnabled(bool e) { y_axis_enabled_ = e; } rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) { 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(QPointF mouse_diff_scaled, bool include_opposing, bool undoable) { // Flip the mouse Y because bezier control points are drawn bottom to top, not top to bottom mouse_diff_scaled.setY(-mouse_diff_scaled.y()); 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_; } NodeInput* input_parent = dragging_bezier_point_->key()->parent(); if (undoable) { QUndoCommand* command = new QUndoCommand(); // Similar to the code in MouseRelease, we manipulated the signalling earlier and need to set the keys back to their // original position to allow the input to signal correctly when the undo command is pushed. input_parent->blockSignals(true); dragging_bezier_point_->key()->set_bezier_control(dragging_bezier_point_->mode(), dragging_bezier_point_start_); new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(), dragging_bezier_point_->mode(), new_bezier_pos, dragging_bezier_point_start_, command); if (include_opposing) { dragging_bezier_point_->key()->set_bezier_control(opposing_type, dragging_bezier_point_opposing_start_); new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(), opposing_type, new_opposing_pos, dragging_bezier_point_opposing_start_, command); } input_parent->blockSignals(false); Core::instance()->undo_stack()->push(command); } else { input_parent->blockSignals(true); 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); input_parent->blockSignals(false); input_parent->parentNode()->InvalidateVisible(input_parent); } } QPointF KeyframeViewBase::GetScaledCursorPos(const QPoint &cursor_pos) { return QPointF(static_cast(cursor_pos.x()) / GetScale(), static_cast(cursor_pos.y()) / y_scale_); } void KeyframeViewBase::ShowContextMenu() { Menu m; MenuShared::instance()->AddItemsForEditMenu(&m); QAction* linear_key_action = nullptr; QAction* bezier_key_action = nullptr; QAction* hold_key_action = nullptr; QList items = scene()->selectedItems(); if (!items.isEmpty()) { bool all_keys_are_same_type = true; NodeKeyframe::Type type = static_cast(items.first())->key()->type(); for (int i=1;i(items.at(i)); KeyframeViewItem* prev_item = static_cast(items.at(i-1)); if (key_item->key()->type() != prev_item->key()->type()) { all_keys_are_same_type = false; break; } } m.addSeparator(); linear_key_action = m.addAction(tr("Linear")); bezier_key_action = m.addAction(tr("Bezier")); hold_key_action = m.addAction(tr("Hold")); if (all_keys_are_same_type) { switch (type) { case NodeKeyframe::kLinear: linear_key_action->setChecked(true); break; case NodeKeyframe::kBezier: bezier_key_action->setChecked(true); break; case NodeKeyframe::kHold: hold_key_action->setChecked(true); break; } } m.addSeparator(); QAction* properties_action = m.addAction(tr("P&roperties")); connect(properties_action, &QAction::triggered, this, &KeyframeViewBase::ShowKeyframePropertiesDialog); } QAction* selected = m.exec(QCursor::pos()); // Process keyframe type changes if (!items.isEmpty()) { if (selected == linear_key_action || selected == bezier_key_action || selected == hold_key_action) { NodeKeyframe::Type new_type; if (selected == hold_key_action) { new_type = NodeKeyframe::kHold; } else if (selected == bezier_key_action) { new_type = NodeKeyframe::kBezier; } else { new_type = NodeKeyframe::kLinear; } QUndoCommand* command = new QUndoCommand(); foreach (QGraphicsItem* item, items) { new KeyframeSetTypeCommand(static_cast(item)->key(), new_type, command); } Core::instance()->undo_stack()->pushIfHasChildren(command); } } } void KeyframeViewBase::ShowKeyframePropertiesDialog() { QList items = scene()->selectedItems(); QList keys; foreach (QGraphicsItem* item, items) { keys.append(static_cast(item)->key()); } if (!keys.isEmpty()) { KeyframePropertiesDialog kd(keys, timebase(), this); kd.exec(); } }