diff --git a/app/core.cpp b/app/core.cpp index edcdb09a0..426d0eab1 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -373,6 +373,9 @@ void Core::StartGUI(bool full_screen) // Since we're starting GUI mode, create a PanelFocusManager (auto-deletes with QObject) PanelManager::CreateInstance(); + // Initialize audio service + AudioManager::CreateInstance(); + // Connect the PanelFocusManager to the application's focus change signal connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), @@ -390,9 +393,6 @@ void Core::StartGUI(bool full_screen) // When a new project is opened, update the mainwindow connect(this, SIGNAL(ProjectOpened(Project*)), main_window_, SLOT(ProjectOpen(Project*))); - // Initialize audio service - AudioManager::CreateInstance(); - // Start autorecovery timer using the config value as its interval SetAutorecoveryInterval(Config::Current()["AutorecoveryInterval"].toInt()); autorecovery_timer_.start(); diff --git a/app/panel/curve/curve.cpp b/app/panel/curve/curve.cpp index 165a332c8..31cea1c17 100644 --- a/app/panel/curve/curve.cpp +++ b/app/panel/curve/curve.cpp @@ -10,10 +10,37 @@ CurvePanel::CurvePanel(QWidget *parent) : widget_ = new CurveWidget(); setWidget(widget_); + connect(widget_, &CurveWidget::TimeChanged, this, &CurvePanel::TimeChanged); + // Set strings Retranslate(); } +void CurvePanel::SetInput(NodeInput *input) +{ + widget_->SetInput(input); +} + +void CurvePanel::SetTimebase(const rational &timebase) +{ + widget_->SetTimebase(timebase); +} + +void CurvePanel::SetTime(const int64_t ×tamp) +{ + widget_->SetTime(timestamp); +} + +void CurvePanel::ZoomIn() +{ + widget_->SetScale(widget_->GetScale() * 2); +} + +void CurvePanel::ZoomOut() +{ + widget_->SetScale(widget_->GetScale() * 0.5); +} + void CurvePanel::changeEvent(QEvent *e) { if (e->type() == QEvent::LanguageChange) { diff --git a/app/panel/curve/curve.h b/app/panel/curve/curve.h index ecdee580d..36b2170a4 100644 --- a/app/panel/curve/curve.h +++ b/app/panel/curve/curve.h @@ -6,9 +6,24 @@ class CurvePanel : public PanelWidget { + Q_OBJECT public: CurvePanel(QWidget* parent); +public slots: + void SetInput(NodeInput* input); + + void SetTimebase(const rational& timebase); + + void SetTime(const int64_t& timestamp); + + virtual void ZoomIn() override; + + virtual void ZoomOut() override; + +signals: + void TimeChanged(const int64_t& timestamp); + protected: virtual void changeEvent(QEvent* e) override; diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index aadfe19d2..6bedce0ea 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -28,6 +28,8 @@ ParamPanel::ParamPanel(QWidget* parent) : view_ = new NodeParamView(this); connect(view_, &NodeParamView::TimeChanged, this, &ParamPanel::TimeChanged); + connect(view_, &NodeParamView::SelectedInputChanged, this, &ParamPanel::SelectedInputChanged); + connect(view_, &NodeParamView::TimebaseChanged, this, &ParamPanel::TimebaseChanged); setWidget(view_); diff --git a/app/panel/param/param.h b/app/panel/param/param.h index 9aff4c9e0..c45b5b2f5 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -42,6 +42,10 @@ public slots: signals: void TimeChanged(const int64_t& timestamp); + void SelectedInputChanged(NodeInput* input); + + void TimebaseChanged(const rational& timebase); + protected: virtual void changeEvent(QEvent* e) override; diff --git a/app/project/item/sequence/sequence.cpp b/app/project/item/sequence/sequence.cpp index d8645b4b6..473550636 100644 --- a/app/project/item/sequence/sequence.cpp +++ b/app/project/item/sequence/sequence.cpp @@ -26,6 +26,7 @@ #include "common/timecodefunctions.h" #include "panel/panelmanager.h" #include "panel/node/node.h" +#include "panel/curve/curve.h" #include "panel/param/param.h" #include "panel/timeline/timeline.h" #include "panel/viewer/viewer.h" @@ -45,6 +46,7 @@ void Sequence::Open(SequencePtr sequence) TimelinePanel* timeline_panel = PanelManager::instance()->MostRecentlyFocused(); NodePanel* node_panel = PanelManager::instance()->MostRecentlyFocused(); ParamPanel* param_panel = PanelManager::instance()->MostRecentlyFocused(); + CurvePanel* curve_panel = PanelManager::instance()->MostRecentlyFocused(); viewer_panel->ConnectViewerNode(sequence->viewer_output_); timeline_panel->ConnectTimelineNode(sequence->timeline_output_); @@ -52,10 +54,16 @@ void Sequence::Open(SequencePtr sequence) connect(timeline_panel, &TimelinePanel::TimeChanged, param_panel, &ParamPanel::SetTime); connect(timeline_panel, &TimelinePanel::TimeChanged, viewer_panel, &ViewerPanel::SetTime); + connect(timeline_panel, &TimelinePanel::TimeChanged, curve_panel, &CurvePanel::SetTime); connect(viewer_panel, &ViewerPanel::TimeChanged, param_panel, &ParamPanel::SetTime); connect(viewer_panel, &ViewerPanel::TimeChanged, timeline_panel, &TimelinePanel::SetTime); + connect(viewer_panel, &ViewerPanel::TimeChanged, curve_panel, &CurvePanel::SetTime); connect(param_panel, &ParamPanel::TimeChanged, viewer_panel, &ViewerPanel::SetTime); connect(param_panel, &ParamPanel::TimeChanged, timeline_panel, &TimelinePanel::SetTime); + connect(param_panel, &ParamPanel::TimeChanged, curve_panel, &CurvePanel::SetTime); + connect(curve_panel, &CurvePanel::TimeChanged, viewer_panel, &ViewerPanel::SetTime); + connect(curve_panel, &CurvePanel::TimeChanged, timeline_panel, &TimelinePanel::SetTime); + connect(curve_panel, &CurvePanel::TimeChanged, param_panel, &ParamPanel::SetTime); } void Sequence::add_default_nodes() diff --git a/app/widget/audiomonitor/audiomonitor.cpp b/app/widget/audiomonitor/audiomonitor.cpp index a6c73404f..1deb8679a 100644 --- a/app/widget/audiomonitor/audiomonitor.cpp +++ b/app/widget/audiomonitor/audiomonitor.cpp @@ -16,9 +16,9 @@ AudioMonitor::AudioMonitor(QWidget *parent) : { clear_timer_.setInterval(kClearTimerInterval); - connect(AudioManager::instance(), SIGNAL(SentSamples(QVector)), this, SLOT(SetValues(QVector))); - connect(&clear_timer_, SIGNAL(timeout()), this, SLOT(Clear())); - connect(&clear_timer_, SIGNAL(timeout()), &clear_timer_, SLOT(stop())); + connect(AudioManager::instance(), &AudioManager::SentSamples, this, &AudioMonitor::SetValues); + connect(&clear_timer_, &QTimer::timeout, this, &AudioMonitor::Clear); + connect(&clear_timer_, &QTimer::timeout, &clear_timer_, &QTimer::stop); } void AudioMonitor::SetValues(QVector values) diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index 4f32d18e7..b716ace43 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -5,41 +5,197 @@ #include "common/qtversionabstraction.h" CurveView::CurveView(QWidget *parent) : - TimelineViewBase(parent) + TimelineViewBase(parent), + y_scale_(1.0) { setAlignment(Qt::AlignLeft | Qt::AlignBottom); setDragMode(RubberBandDrag); setViewportUpdateMode(FullViewportUpdate); text_padding_ = QFontMetricsWidth(fontMetrics(), QStringLiteral("i")); + + minimum_grid_space_ = QFontMetricsWidth(fontMetrics(), QStringLiteral("00000")); +} + +void CurveView::Clear() +{ + QMap::const_iterator iterator; + + for (iterator=key_item_map_.begin();iterator!=key_item_map_.end();iterator++) { + delete iterator.value(); + } + + foreach (QGraphicsLineItem* line, lines_) { + delete line; + } + + key_item_map_.clear(); + lines_.clear(); +} + +void CurveView::SetXScale(const double& x_scale) +{ + SetScale(x_scale); +} + +void CurveView::SetYScale(const double &y_scale) +{ + y_scale_ = y_scale; + viewport()->update(); } void CurveView::drawBackground(QPainter *painter, const QRectF &rect) { + if (timebase().isNull()) { + return; + } + QVector lines; - //painter->setPen(palette().window().color()); + double x_interval = timebase().flipped().toDouble(); + double y_interval = 100.0; - int grid_interval = 100; + int x_grid_interval, y_grid_interval; - int x_start = qCeil(rect.left() / grid_interval) * grid_interval; - int y_start = qCeil(rect.top() / grid_interval) * grid_interval; + painter->setPen(QPen(palette().window().color(), 1)); - QPoint viewport_edges(0, qRound(rect.height())); - QPointF scene_edges = mapToScene(viewport_edges); - //QRectF scene_edges = mapToScene(viewport()->geometry()).boundingRect(); + do { + x_grid_interval = qRound(x_interval * scale_ * timebase_dbl()); + x_interval *= 2.0; + } while (x_grid_interval < minimum_grid_space_); + + do { + y_grid_interval = qRound(y_interval * y_scale_); + y_interval *= 2.0; + } while (y_grid_interval < minimum_grid_space_); + + int x_start = qCeil(rect.left() / x_grid_interval) * x_grid_interval; + int y_start = qCeil(rect.top() / y_grid_interval) * y_grid_interval; + + QPointF scene_bottom_left = mapToScene(QPoint(0, qRound(rect.height()))); + QPointF scene_top_right = mapToScene(QPoint(qRound(rect.width()), 0)); // Add vertical lines - for (int i=x_start;idrawText(i + text_padding_, qRound(scene_edges.y()) - text_padding_, QString::number(i)); + for (int i=x_start;i(i) / scale_ / timebase_dbl()); + painter->drawText(i + text_padding_, qRound(scene_bottom_left.y()) - text_padding_, QString::number(value)); lines.append(QLine(i, qRound(rect.top()), i, qRound(rect.bottom()))); } // Add horizontal lines - for (int i=y_start;idrawText(qRound(scene_edges.x()) + text_padding_, i - text_padding_, QString::number(-i)); + for (int i=y_start;i(i) / y_scale_); + painter->drawText(qRound(scene_bottom_left.x()) + text_padding_, i - text_padding_, QString::number(-value)); lines.append(QLine(qRound(rect.left()), i, qRound(rect.right()), i)); } + // Draw grid painter->drawLines(lines); + + // Draw keyframe lines + QList keys = GetKeyframesSortedByTime(); + painter->setPen(QPen(palette().text().color(), 2)); + if (!keys.isEmpty()) { + QVector keyframe_lines; + + // Draw straight line leading to first keyframe + QPointF first_key_pos = key_item_map_.value(keys.first())->center_pos(); + keyframe_lines.append(QLineF(QPointF(scene_bottom_left.x(), first_key_pos.y()), first_key_pos)); + + // Draw lines between each keyframe + for (int i=1;itype() == 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())); + } else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { + // Draw a cubic bezier + } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) { + // Draw a quadratic bezier + } else { + // Linear to linear + keyframe_lines.append(QLineF(before_item->center_pos(), after_item->center_pos())); + } + } + + // Draw straight line leading from end keyframe + QPointF last_key_pos = key_item_map_.value(keys.last())->center_pos(); + keyframe_lines.append(QLineF(last_key_pos, QPointF(scene_top_right.x(), last_key_pos.y()))); + + painter->drawLines(keyframe_lines); + } +} + +QList CurveView::GetKeyframesSortedByTime() +{ + QList sorted; + + QMap::const_iterator iterator; + + for (iterator=key_item_map_.begin();iterator!=key_item_map_.end();iterator++) { + NodeKeyframe* key = iterator.key(); + bool inserted = false; + + for (int i=0;itime() > key->time()) { + sorted.insert(i, key); + inserted = true; + break; + } + } + + if (!inserted) { + sorted.append(key); + } + } + + return sorted; +} + +qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key) +{ + return -key->value().toDouble() * y_scale_; +} + +void CurveView::SetItemYFromKeyframeValue(NodeKeyframe *key, KeyframeViewItem *item) +{ + item->SetOverrideY(GetItemYFromKeyframeValue(key)); +} + +void CurveView::KeyframeValueChanged() +{ + NodeKeyframe* key = static_cast(sender()); + KeyframeViewItem* item = key_item_map_.value(key); + + SetItemYFromKeyframeValue(key, item); +} + +void CurveView::AddKeyframe(NodeKeyframePtr key) +{ + KeyframeViewItem* item = new KeyframeViewItem(key); + SetItemYFromKeyframeValue(key.get(), item); + item->SetScale(scale_); + scene()->addItem(item); + key_item_map_.insert(key.get(), item); + + connect(key.get(), &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged); +} + +void CurveView::RemoveKeyframe(NodeKeyframePtr key) +{ + disconnect(key.get(), &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged); + + delete key_item_map_.take(key.get()); } diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index 8f8ada0f9..f277e52d7 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -1,6 +1,8 @@ #ifndef CURVEVIEW_H #define CURVEVIEW_H +#include "node/keyframe.h" +#include "widget/keyframeview/keyframeviewitem.h" #include "widget/timelinewidget/view/timelineviewbase.h" class CurveView : public TimelineViewBase @@ -8,12 +10,42 @@ class CurveView : public TimelineViewBase public: CurveView(QWidget* parent = nullptr); + void Clear(); + + void SetXScale(const double& x_scale); + + void SetYScale(const double& y_scale); + +public slots: + void AddKeyframe(NodeKeyframePtr key); + + void RemoveKeyframe(NodeKeyframePtr key); + protected: virtual void drawBackground(QPainter* painter, const QRectF& rect) override; private: + QList GetKeyframesSortedByTime(); + + qreal GetItemYFromKeyframeValue(NodeKeyframe* key); + + void SetItemYFromKeyframeValue(NodeKeyframe* key, KeyframeViewItem* item); + + void AdjustLines(); + int text_padding_; + double y_scale_; + + int minimum_grid_space_; + + QMap key_item_map_; + + QList lines_; + +private slots: + void KeyframeValueChanged(); + }; #endif // CURVEVIEW_H diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 5cdeb1e7f..2e553960d 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -1,12 +1,20 @@ #include "curvewidget.h" +#include #include #include +#include "core.h" +#include "common/qtversionabstraction.h" +#include "common/timecodefunctions.h" +#include "node/node.h" +#include "widget/keyframeview/keyframeviewundo.h" #include "widget/nodeparamview/nodeparamviewkeyframecontrol.h" CurveWidget::CurveWidget(QWidget *parent) : - QWidget(parent) + QWidget(parent), + input_(nullptr), + bridge_(nullptr) { QVBoxLayout* layout = new QVBoxLayout(this); @@ -25,21 +33,235 @@ CurveWidget::CurveWidget(QWidget *parent) : linear_button_->setCheckable(true); linear_button_->setEnabled(false); top_controls->addWidget(linear_button_); + connect(linear_button_, &QPushButton::clicked, this, &CurveWidget::KeyframeTypeButtonTriggered); bezier_button_ = new QPushButton(tr("Bezier")); bezier_button_->setCheckable(true); bezier_button_->setEnabled(false); top_controls->addWidget(bezier_button_); + connect(bezier_button_, &QPushButton::clicked, this, &CurveWidget::KeyframeTypeButtonTriggered); hold_button_ = new QPushButton(tr("Hold")); hold_button_->setCheckable(true); hold_button_->setEnabled(false); top_controls->addWidget(hold_button_); + connect(hold_button_, &QPushButton::clicked, this, &CurveWidget::KeyframeTypeButtonTriggered); layout->addLayout(top_controls); - view_ = new CurveView(); - layout->addWidget(view_); + // We use a separate layout for the ruler+view combination so that there's no spacing between them + QVBoxLayout* ruler_view_layout = new QVBoxLayout(); + ruler_view_layout->setMargin(0); + ruler_view_layout->setSpacing(0); - layout->addWidget(new QLabel("Values")); + ruler_ = new TimeRuler(); + ruler_view_layout->addWidget(ruler_); + + view_ = new CurveView(); + ruler_view_layout->addWidget(view_); + + layout->addLayout(ruler_view_layout); + + // Connect ruler and view together + connect(ruler_, &TimeRuler::TimeChanged, view_, &CurveView::SetTime); + connect(view_, &CurveView::TimeChanged, ruler_, &TimeRuler::SetTime); + connect(ruler_, &TimeRuler::TimeChanged, this, &CurveWidget::TimeChanged); + connect(view_, &CurveView::TimeChanged, this, &CurveWidget::TimeChanged); + connect(ruler_, &TimeRuler::TimeChanged, this, &CurveWidget::UpdateBridgeTime); + connect(view_, &CurveView::TimeChanged, this, &CurveWidget::UpdateBridgeTime); + connect(view_->scene(), &QGraphicsScene::selectionChanged, this, &CurveWidget::SelectionChanged); + + widget_bridge_layout_ = new QHBoxLayout(); + widget_bridge_layout_->addStretch(); + input_label_ = new QLabel(); + widget_bridge_layout_->addWidget(input_label_); + widget_bridge_layout_->addStretch(); + layout->addLayout(widget_bridge_layout_); + + SetScale(120.0); +} + +CurveWidget::~CurveWidget() +{ + // Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after the buttons have been destroyed + view_->Clear(); +} + +void CurveWidget::SetInput(NodeInput *input) +{ + if (bridge_) { + foreach (QWidget* bridge_widget, bridge_->widgets()) { + delete bridge_widget; + } + delete bridge_; + bridge_ = nullptr; + } + + if (input_) { + disconnect(input_, &NodeInput::KeyframeAdded, view_, &CurveView::AddKeyframe); + disconnect(input_, &NodeInput::KeyframeRemoved, view_, &CurveView::RemoveKeyframe); + } + + view_->Clear(); + + input_ = input; + + if (input_) { + bridge_ = new NodeParamViewWidgetBridge(input_, this); + + for (int i=0;iwidgets().size();i++) { + // Insert between two stretches to center the widget + widget_bridge_layout_->insertWidget(2 + i, bridge_->widgets().at(i)); + } + + connect(input_, &NodeInput::KeyframeAdded, view_, &CurveView::AddKeyframe); + connect(input_, &NodeInput::KeyframeRemoved, view_, &CurveView::RemoveKeyframe); + + foreach (NodeKeyframePtr key, input_->keyframes()) { + view_->AddKeyframe(key); + } + } + + UpdateInputLabel(); +} + +void CurveWidget::SetTimebase(const rational &timebase) +{ + ruler_->SetTimebase(timebase); + view_->SetTimebase(timebase); +} + +void CurveWidget::SetTime(const int64_t ×tamp) +{ + ruler_->SetTime(timestamp); + view_->SetTime(timestamp); + UpdateBridgeTime(timestamp); +} + +const double &CurveWidget::GetScale() +{ + return ruler_->scale(); +} + +void CurveWidget::SetScale(const double &scale) +{ + ruler_->SetScale(scale); + view_->SetXScale(scale); +} + +void CurveWidget::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + UpdateInputLabel(); + } + QWidget::changeEvent(e); +} + +void CurveWidget::UpdateInputLabel() +{ + if (input_) { + input_label_->setText(QStringLiteral("%1 :: %2:").arg(input_->parentNode()->Name(), input_->name())); + } else { + input_label_->clear(); + } +} + +void CurveWidget::SetKeyframeButtonEnabled(bool enable) +{ + linear_button_->setEnabled(enable); + bezier_button_->setEnabled(enable); + hold_button_->setEnabled(enable); +} + +void CurveWidget::SetKeyframeButtonChecked(bool checked) +{ + linear_button_->setChecked(checked); + bezier_button_->setChecked(checked); + hold_button_->setChecked(checked); +} + +void CurveWidget::SetKeyframeButtonCheckedFromType(NodeKeyframe::Type type) +{ + linear_button_->setChecked(type == NodeKeyframe::kLinear); + bezier_button_->setChecked(type == NodeKeyframe::kBezier); + hold_button_->setChecked(type == NodeKeyframe::kHold); +} + +void CurveWidget::UpdateBridgeTime(const int64_t ×tamp) +{ + if (!input_) { + return; + } + + rational time = Timecode::timestamp_to_time(timestamp, view_->timebase()); + bridge_->SetTime(time); +} + +void CurveWidget::SelectionChanged() +{ + QList selected = view_->scene()->selectedItems(); + + SetKeyframeButtonChecked(false); + SetKeyframeButtonEnabled(!selected.isEmpty()); + + if (!selected.isEmpty()) { + bool all_same_type = true; + NodeKeyframe::Type type = static_cast(selected.first())->key()->type(); + + for (int i=1;i(selected.at(i-1)); + KeyframeViewItem* this_item = static_cast(selected.at(i)); + + if (prev_item->key()->type() != this_item->key()->type()) { + all_same_type = false; + break; + } + } + + if (all_same_type) { + SetKeyframeButtonCheckedFromType(type); + } + } +} + +void CurveWidget::KeyframeTypeButtonTriggered(bool checked) +{ + QPushButton* key_btn = static_cast(sender()); + + if (!checked) { + // Keyframe buttons cannot be checked off, we undo this action here + key_btn->setChecked(true); + return; + } + + // Get selected items and do nothing if there are none + QList selected = view_->scene()->selectedItems(); + if (selected.isEmpty()) { + return; + } + + // Set all selected keyframes to this type + NodeKeyframe::Type new_type; + + // Determine which type to set + if (key_btn == bezier_button_) { + new_type = NodeKeyframe::kBezier; + } else if (key_btn == hold_button_) { + new_type = NodeKeyframe::kHold; + } else { + new_type = NodeKeyframe::kLinear; + } + + // Ensure only the appropriate button is checked + SetKeyframeButtonCheckedFromType(new_type); + + QUndoCommand* command = new QUndoCommand(); + + foreach (QGraphicsItem* item, selected) { + KeyframeViewItem* key_item = static_cast(item); + + new KeyframeSetTypeCommand(key_item->key(), new_type, command); + } + + Core::instance()->undo_stack()->push(command); } diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index 21758e24d..be121f8a2 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -1,25 +1,72 @@ #ifndef CURVEWIDGET_H #define CURVEWIDGET_H +#include #include #include #include "curveview.h" +#include "node/input.h" +#include "widget/nodeparamview/nodeparamviewwidgetbridge.h" +#include "widget/timeruler/timeruler.h" class CurveWidget : public QWidget { + Q_OBJECT public: CurveWidget(QWidget* parent = nullptr); + virtual ~CurveWidget() override; + + void SetInput(NodeInput* input); + + void SetTimebase(const rational& timebase); + + void SetTime(const int64_t& timestamp); + + const double& GetScale(); + void SetScale(const double& scale); + +signals: + void TimeChanged(const int64_t& timestamp); + +protected: + virtual void changeEvent(QEvent *) override; + private: + void UpdateInputLabel(); + + void SetKeyframeButtonEnabled(bool enable); + + void SetKeyframeButtonChecked(bool checked); + + void SetKeyframeButtonCheckedFromType(NodeKeyframe::Type type); + QPushButton* linear_button_; QPushButton* bezier_button_; QPushButton* hold_button_; + TimeRuler* ruler_; + CurveView* view_; + NodeInput* input_; + + QLabel* input_label_; + + QHBoxLayout* widget_bridge_layout_; + + NodeParamViewWidgetBridge* bridge_; + +private slots: + void UpdateBridgeTime(const int64_t& timestamp); + + void SelectionChanged(); + + void KeyframeTypeButtonTriggered(bool checked); + }; #endif // CURVEWIDGET_H diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index d64826f63..b6c88e7db 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -37,7 +37,8 @@ void KeyframeView::AddKeyframe(NodeKeyframePtr key, int y) QPoint local_pt = mapFromGlobal(global_pt); QPointF scene_pt = mapToScene(local_pt); - KeyframeViewItem* item = new KeyframeViewItem(key, scene_pt.y()); + KeyframeViewItem* item = new KeyframeViewItem(key); + item->SetOverrideY(scene_pt.y()); item->SetScale(scale_); item_map_.insert(key.get(), item); scene()->addItem(item); @@ -45,7 +46,7 @@ void KeyframeView::AddKeyframe(NodeKeyframePtr key, int y) void KeyframeView::RemoveKeyframe(NodeKeyframePtr key) { - delete item_map_.value(key.get()); + delete item_map_.take(key.get()); } void KeyframeView::mousePressEvent(QMouseEvent *event) diff --git a/app/widget/keyframeview/keyframeviewitem.cpp b/app/widget/keyframeview/keyframeviewitem.cpp index 8a8afacc0..de8da1ef4 100644 --- a/app/widget/keyframeview/keyframeviewitem.cpp +++ b/app/widget/keyframeview/keyframeviewitem.cpp @@ -7,11 +7,11 @@ #include "common/qtversionabstraction.h" -KeyframeViewItem::KeyframeViewItem(NodeKeyframePtr key, qreal vcenter, QGraphicsItem *parent) : +KeyframeViewItem::KeyframeViewItem(NodeKeyframePtr key, QGraphicsItem *parent) : QGraphicsRectItem(parent), key_(key), scale_(1.0), - middle_(vcenter) + vert_center_(0) { keyframe_size_ = QFontMetricsWidth(qApp->fontMetrics(), "Oi"); setFlag(QGraphicsItem::ItemIsSelectable); @@ -24,6 +24,12 @@ KeyframeViewItem::KeyframeViewItem(NodeKeyframePtr key, qreal vcenter, QGraphics UpdatePos(); } +void KeyframeViewItem::SetOverrideY(qreal vertical_center) +{ + vert_center_ = vertical_center; + UpdatePos(); +} + void KeyframeViewItem::SetScale(double scale) { scale_ = scale; @@ -35,6 +41,11 @@ 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); @@ -71,7 +82,7 @@ void KeyframeViewItem::UpdatePos() { double x_center = key_->time().toDouble() * scale_; - setPos(x_center - keyframe_size_/2, middle_ - keyframe_size_/2); + setPos(x_center - keyframe_size_/2, vert_center_ - keyframe_size_/2); } void KeyframeViewItem::Redraw() diff --git a/app/widget/keyframeview/keyframeviewitem.h b/app/widget/keyframeview/keyframeviewitem.h index 721b6e6b9..7508bae07 100644 --- a/app/widget/keyframeview/keyframeviewitem.h +++ b/app/widget/keyframeview/keyframeviewitem.h @@ -9,12 +9,16 @@ class KeyframeViewItem : public QObject, public QGraphicsRectItem { Q_OBJECT public: - KeyframeViewItem(NodeKeyframePtr key, qreal vcenter, QGraphicsItem *parent = nullptr); + KeyframeViewItem(NodeKeyframePtr key, QGraphicsItem *parent = nullptr); + + void SetOverrideY(qreal vertical_center); void SetScale(double scale); NodeKeyframePtr key() const; + QPointF center_pos() const; + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; @@ -23,7 +27,7 @@ private: double scale_; - qreal middle_; + qreal vert_center_; int keyframe_size_; diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 952121891..744db0ac4 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -132,6 +132,7 @@ void NodeParamView::SetNodes(QList nodes) connect(item, &NodeParamViewItem::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); connect(item, &NodeParamViewItem::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::ItemRequestedTimeChanged); + connect(item, &NodeParamViewItem::InputClicked, this, &NodeParamView::SelectedInputChanged); items_.append(item); @@ -147,6 +148,13 @@ void NodeParamView::SetNodes(QList nodes) } SetTime(0); + + // FIXME: Test code only! + if (nodes_.isEmpty()) { + emit SelectedInputChanged(nullptr); + } else { + emit SelectedInputChanged(static_cast(nodes_.first()->parameters().first())); + } } void NodeParamView::resizeEvent(QResizeEvent *event) @@ -184,6 +192,8 @@ void NodeParamView::SetTimebase(const rational &timebase) { ruler_->SetTimebase(timebase); keyframe_view_->SetTimebase(timebase); + + emit TimebaseChanged(timebase); } void NodeParamView::UpdateItemTime(const int64_t ×tamp) diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 96bfca419..d4310c353 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -47,6 +47,10 @@ public slots: signals: void TimeChanged(const int64_t& timestamp); + void SelectedInputChanged(NodeInput* input); + + void TimebaseChanged(const rational& timebase); + protected: virtual void resizeEvent(QResizeEvent *event) override; diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index be4556e96..64eaea125 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -137,7 +137,8 @@ void NodeParamViewItem::SetupUI() NodeInput* input = static_cast(param); // Add descriptor label - QLabel* param_label = new QLabel(); + ClickableLabel* param_label = new ClickableLabel(); + connect(param_label, &ClickableLabel::MouseClicked, this, &NodeParamViewItem::LabelClicked); param_lbls_.append(param_label); label_map_.insert(input, param_label); @@ -364,6 +365,17 @@ void NodeParamViewItem::GoToNextKey() } } +void NodeParamViewItem::LabelClicked() +{ + ClickableLabel* lbl = static_cast(sender()); + + NodeInput* input = label_map_.key(lbl, nullptr); + + if (input) { + emit InputClicked(input); + } +} + NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : QWidget(parent) { diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 7528db49e..0177fa8bc 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -30,6 +30,7 @@ #include "node/node.h" #include "nodeparamviewkeyframecontrol.h" #include "nodeparamviewwidgetbridge.h" +#include "widget/clickablelabel/clickablelabel.h" class NodeParamViewItemTitleBar : public QWidget { public: @@ -57,6 +58,8 @@ signals: void RequestSetTime(const rational& time); + void InputClicked(NodeInput* input); + protected: virtual void changeEvent(QEvent *e) override; @@ -77,7 +80,7 @@ private: QLabel* title_bar_lbl_; - QVector param_lbls_; + QVector param_lbls_; QPushButton* title_bar_collapse_btn_; @@ -110,6 +113,8 @@ private slots: void GoToNextKey(); + void LabelClicked(); + }; #endif // NODEPARAMVIEWITEM_H diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 6edb2e17d..af63ab0d1 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -31,6 +31,10 @@ void NodeParamViewWidgetBridge::SetTime(const rational &time) { time_ = time; + if (!input_) { + return; + } + // We assume the first data type is the "primary" type switch (input_->data_type()) { // None of these inputs have applicable UI widgets diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 7b464544a..272b03b8d 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -248,13 +248,14 @@ void TimelineWidget::PointerTool::InitiateDrag(const TimelineCoordinate &mouse_p Timeline::MovementMode trim_mode = Timeline::kNone; // FIXME: Hardcoded number - const int kTrimHandle = 10; + double kTrimHandle = qMax(10.0, parent()->TimeToScene(parent()->timebase())); qreal mouse_x = parent()->TimeToScene(mouse_pos.GetFrame()); - if (trimming_allowed_ && mouse_x < clicked_item->x() + kTrimHandle) { + + if (trimming_allowed_ && mouse_x <= clicked_item->x() + kTrimHandle) { trim_mode = Timeline::kTrimIn; - } else if (trimming_allowed_ && mouse_x > clicked_item->x() + clicked_item->rect().right() - kTrimHandle) { + } else if (trimming_allowed_ && mouse_x >= clicked_item->x() + clicked_item->rect().right() - kTrimHandle) { trim_mode = Timeline::kTrimOut; } else if (movement_allowed_) { // Some derived classes don't allow movement diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index ddb743e65..1a2d03886 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -71,7 +71,9 @@ MainWindow::MainWindow(QWidget *parent) : curve_panel_ = PanelManager::instance()->CreatePanel(this); addDockWidget(Qt::BottomDockWidgetArea, curve_panel_); - connect(node_panel_, SIGNAL(SelectionChanged(QList)), param_panel_, SLOT(SetNodes(QList))); + connect(node_panel_, &NodePanel::SelectionChanged, param_panel_, &ParamPanel::SetNodes); + connect(param_panel_, &ParamPanel::SelectedInputChanged, curve_panel_, &CurvePanel::SetInput); + connect(param_panel_, &ParamPanel::TimebaseChanged, curve_panel_, &CurvePanel::SetTimebase); } void MainWindow::SetFullscreen(bool fullscreen) @@ -154,6 +156,6 @@ void MainWindow::SetDefaultLayout() Qt::Horizontal); resizeDocks({project_panel_, tool_panel_, timeline_panel_, audio_monitor_panel_}, - {width()/4, 0, width(), 0}, + {width()/4, 1, width(), 1}, Qt::Horizontal); }