diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 34fea246d..5a9c50c64 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -49,18 +49,26 @@ NodeParamView::NodeParamView(QWidget *parent) : splitter->addWidget(scroll_area); // Param widget - param_widget_area_ = new QWidget(); - scroll_area->setWidget(param_widget_area_); + param_widget_container_ = new NodeParamViewParamContainer(); + connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar); + scroll_area->setWidget(param_widget_container_); - // Set up scroll area layout - param_layout_ = new QVBoxLayout(param_widget_area_); - param_layout_->setSpacing(0); + param_widget_area_ = new QMainWindow(); - // KeyframeView is offset by a ruler, so to stay synchronized with it, we should be too - param_layout_->setContentsMargins(0, ruler()->height(), 0, 0); + // Disable dock widgets from tabbing and disable glitchy animations + param_widget_area_->setDockOptions(static_cast(0)); - // Add a stretch to allow empty space at the bottom of the layout - param_layout_->addStretch(); + // HACK: Hide the main window separators (unfortunately the cursors still appear) + param_widget_area_->setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}")); + + QVBoxLayout* param_widget_container_layout = new QVBoxLayout(param_widget_container_); + QMargins param_widget_margin = param_widget_container_layout->contentsMargins(); + param_widget_margin.setTop(ruler()->height()); + param_widget_container_layout->setContentsMargins(param_widget_margin); + param_widget_container_layout->setSpacing(0); + param_widget_container_layout->addWidget(param_widget_area_); + + param_widget_container_layout->addStretch(INT_MAX); // Set up keyframe view QWidget* keyframe_area = new QWidget(); @@ -100,9 +108,8 @@ NodeParamView::NodeParamView(QWidget *parent) : layout->addWidget(vertical_scrollbar_); // Connect scrollbars together - connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, vertical_scrollbar_, &QScrollBar::setRange); - connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeParamView::ForceKeyframeViewToScroll); - + //connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, vertical_scrollbar_, &QScrollBar::setRange); + //connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeParamView::ForceKeyframeViewToScroll); connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue); connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); @@ -125,27 +132,29 @@ NodeParamView::NodeParamView(QWidget *parent) : void NodeParamView::SelectNodes(const QList &nodes) { foreach (Node* n, nodes) { - NodeParamViewItem* item = new NodeParamViewItem(n); + NodeParamViewItem* item = new NodeParamViewItem(n, param_widget_area_); - // Insert the widget before the stretch - param_layout_->insertWidget(param_layout_->count() - 1, item); + item->setAllowedAreas(Qt::LeftDockWidgetArea); + item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); 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::InputDoubleClicked, this, &NodeParamView::InputDoubleClicked); connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode); + connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate); // Set time target item->SetTimeTarget(GetTimeTarget()); items_.insert(n, item); + param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item); } UpdateItemTime(GetTimestamp()); // Re-arrange keyframes - QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection); + QueueKeyframePositionUpdate(); } void NodeParamView::DeselectNodes(const QList &nodes) @@ -159,7 +168,7 @@ void NodeParamView::DeselectNodes(const QList &nodes) } // Re-arrange keyframes - QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection); + QueueKeyframePositionUpdate(); } void NodeParamView::resizeEvent(QResizeEvent *event) @@ -167,6 +176,8 @@ void NodeParamView::resizeEvent(QResizeEvent *event) QWidget::resizeEvent(event); vertical_scrollbar_->setPageStep(vertical_scrollbar_->height()); + + UpdateGlobalScrollBar(); } void NodeParamView::ScaleChangedEvent(const double &scale) @@ -223,14 +234,22 @@ void NodeParamView::UpdateItemTime(const int64_t ×tamp) } } +void NodeParamView::QueueKeyframePositionUpdate() +{ + QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection); +} + void NodeParamView::ItemRequestedTimeChanged(const rational &time) { SetTimeAndSignal(Timecode::time_to_timestamp(time, keyframe_view_->timebase())); } -void NodeParamView::ForceKeyframeViewToScroll() +void NodeParamView::UpdateGlobalScrollBar() { - keyframe_view_->SetMaxScroll(param_widget_area_->height() - ruler()->height()); + int height_offscreen = param_widget_container_->height() - ruler()->height(); + + keyframe_view_->SetMaxScroll(height_offscreen); + vertical_scrollbar_->setRange(0, height_offscreen - keyframe_view_->height()); } void NodeParamView::PlaceKeyframesOnView() diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 1e3889b0c..6abc18235 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -21,6 +21,7 @@ #ifndef NODEPARAMVIEW_H #define NODEPARAMVIEW_H +#include #include #include @@ -31,6 +32,28 @@ OLIVE_NAMESPACE_ENTER +class NodeParamViewParamContainer : public QWidget +{ + Q_OBJECT +public: + NodeParamViewParamContainer(QWidget* parent = nullptr) : + QWidget(parent) + { + } + +protected: + virtual void resizeEvent(QResizeEvent *event) override + { + QWidget::resizeEvent(event); + + emit Resized(event->size().height()); + } + +signals: + void Resized(int new_height); + +}; + class NodeParamView : public TimeBasedWidget { Q_OBJECT @@ -66,7 +89,7 @@ protected: private: void UpdateItemTime(const int64_t ×tamp); - QVBoxLayout* param_layout_; + void QueueKeyframePositionUpdate(); KeyframeView* keyframe_view_; @@ -76,12 +99,16 @@ private: int last_scroll_val_; - QWidget* param_widget_area_; + NodeParamViewParamContainer* param_widget_container_; + + // This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows + // docking windows + QMainWindow* param_widget_area_; private slots: void ItemRequestedTimeChanged(const rational& time); - void ForceKeyframeViewToScroll(); + void UpdateGlobalScrollBar(); void PlaceKeyframesOnView(); diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index d9093079e..2c436f305 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -32,16 +32,11 @@ OLIVE_NAMESPACE_ENTER NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : - QWidget(parent), + QDockWidget(parent), node_(node) { - QVBoxLayout* main_layout = new QVBoxLayout(this); - main_layout->setSpacing(0); - main_layout->setMargin(0); - // Create title bar widget title_bar_ = new NodeParamViewItemTitleBar(this); - title_bar_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); QHBoxLayout* title_bar_layout = new QHBoxLayout(title_bar_); @@ -52,7 +47,7 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : title_bar_layout->addWidget(title_bar_lbl_); // Add title bar to widget - main_layout->addWidget(title_bar_); + this->setTitleBarWidget(title_bar_); // Create and add contents widget QVector inputs; @@ -70,11 +65,24 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); connect(body_, &NodeParamViewItemBody::KeyframeAdded, this, &NodeParamViewItem::KeyframeAdded); connect(body_, &NodeParamViewItemBody::KeyframeRemoved, this, &NodeParamViewItem::KeyframeRemoved); - connect(title_bar_collapse_btn_, &QPushButton::toggled, body_, &NodeParamViewItemBody::setVisible); - main_layout->addWidget(body_); + connect(title_bar_collapse_btn_, &QPushButton::toggled, this, &NodeParamViewItem::SetExpanded); + connect(title_bar_, &NodeParamViewItemTitleBar::DoubleClicked, this, &NodeParamViewItem::ToggleExpanded); + + QWidget* body_container = new QWidget(); + body_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + QHBoxLayout* body_container_layout = new QHBoxLayout(body_container); + body_container_layout->setSpacing(0); + body_container_layout->setMargin(0); + body_container_layout->addWidget(body_); + this->setWidget(body_container); connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate); + setBackgroundRole(QPalette::Base); + setAutoFillBackground(true); + + setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + Retranslate(); } @@ -122,21 +130,55 @@ void NodeParamViewItem::Retranslate() body_->Retranslate(); } -NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : - QWidget(parent) +void NodeParamViewItem::SetExpanded(bool e) { + body_->setVisible(e); + title_bar_->SetBorderVisible(e); + title_bar_collapse_btn_->setChecked(e); +} + +bool NodeParamViewItem::IsExpanded() const +{ + return body_->isVisible(); +} + +void NodeParamViewItem::ToggleExpanded() +{ + SetExpanded(!IsExpanded()); +} + +NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : + QWidget(parent), + draw_border_(true) +{ +} + +void NodeParamViewItemTitleBar::SetBorderVisible(bool e) +{ + draw_border_ = e; + + update(); } void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); - QPainter p(this); + if (draw_border_) { + QPainter p(this); - // Draw bottom border using text color - int bottom = height() - 1; - p.setPen(palette().text().color()); - p.drawLine(0, bottom, width(), bottom); + // Draw bottom border using text color + int bottom = height() - 1; + p.setPen(palette().text().color()); + p.drawLine(0, bottom, width(), bottom); + } +} + +void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + QWidget::mouseDoubleClickEvent(event); + + emit DoubleClicked(); } NodeParamViewItemBody::NodeParamViewItemBody(const QVector &inputs, QWidget *parent) : diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 6d6996219..e19fc0867 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -21,6 +21,7 @@ #ifndef NODEPARAMVIEWITEM_H #define NODEPARAMVIEWITEM_H +#include #include #include #include @@ -36,12 +37,25 @@ OLIVE_NAMESPACE_ENTER -class NodeParamViewItemTitleBar : public QWidget { +class NodeParamViewItemTitleBar : public QWidget +{ + Q_OBJECT public: NodeParamViewItemTitleBar(QWidget* parent = nullptr); + void SetBorderVisible(bool e); + +signals: + void DoubleClicked(); + protected: virtual void paintEvent(QPaintEvent *event) override; + + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + +private: + bool draw_border_; + }; class NodeParamViewItemBody : public QWidget { @@ -99,7 +113,7 @@ private slots: }; -class NodeParamViewItem : public QWidget +class NodeParamViewItem : public QDockWidget { Q_OBJECT public: @@ -111,6 +125,8 @@ public: Node* GetNode() const; + bool IsExpanded() const; + public slots: void SignalAllKeyframes(); @@ -125,6 +141,11 @@ signals: void RequestSelectNode(const QList& node); +public slots: + void SetExpanded(bool e); + + void ToggleExpanded(); + protected: virtual void changeEvent(QEvent *e) override;