diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index e1b93d671..16ce43556 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -16,11 +16,14 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} + clamp.h core.h core.cpp + lerp.h main.cpp rational.h rational.cpp + qobjectlistcast.h ) add_subdirectory(decoder) diff --git a/app/clamp.h b/app/clamp.h new file mode 100644 index 000000000..b87fe1657 --- /dev/null +++ b/app/clamp.h @@ -0,0 +1,17 @@ +#ifndef CLAMP_H +#define CLAMP_H + +template +T clamp(T value, T minimum, T maximum) { + if (value < minimum) { + return minimum; + } + + if (value > maximum) { + return maximum; + } + + return value; +} + +#endif // CLAMP_H diff --git a/app/lerp.h b/app/lerp.h new file mode 100644 index 000000000..f84ecbcf9 --- /dev/null +++ b/app/lerp.h @@ -0,0 +1,9 @@ +#ifndef LERP_H +#define LERP_H + +template +T lerp(T a, T b, double t) { + return (a * (1.0 - t)) + (b * t); +} + +#endif // LERP_H diff --git a/app/node/graph.cpp b/app/node/graph.cpp index fc1ddf527..3ea32b0b9 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -20,6 +20,8 @@ #include "graph.h" +#include "qobjectlistcast.h" + NodeGraph::NodeGraph() { @@ -34,3 +36,8 @@ void NodeGraph::set_name(const QString &name) { name_ = name; } + +QList NodeGraph::nodes() +{ + return static_qobjectlist_cast(children()); +} diff --git a/app/node/graph.h b/app/node/graph.h index 73b38d8b4..520688262 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -23,6 +23,8 @@ #include +#include "node/node.h" + class NodeGraph : public QObject { public: @@ -31,6 +33,8 @@ public: const QString& name(); void set_name(const QString& name); + QList nodes(); + private: QString name_; }; diff --git a/app/node/node.cpp b/app/node/node.cpp index 2159d236f..e079616df 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -20,6 +20,8 @@ #include "node.h" +#include "qobjectlistcast.h" + Node::Node(QObject *parent) : QObject(parent) { @@ -39,33 +41,22 @@ QString Node::Description() void Node::InvalidateCache(const rational &start_range, const rational &end_range) { - Q_UNUSED(start_range) - Q_UNUSED(end_range) - - /* - ParamList params = Parameters(); + QList params = parameters(); + // Loop through all parameters (there should be no children that are not NodeParams) for (int i=0;itype() == NodeParam::kOutput - && param->) { - + // If the Node is an output, relay the signal to any Nodes that are connected to it + if (param->type() == NodeParam::kOutput) { + for (int i=0;iedges().size();i++) { + param->edges().at(i)->input()->parent()->InvalidateCache(start_range, end_range); + } } } - */ } -Node::ParamList Node::Parameters() +QList Node::parameters() { - const QObjectList& child_list = children(); - - ParamList params; - params.reserve(child_list.size()); - - for (int i=0;i(child_list.at(i)); - } - - return params; + return static_qobjectlist_cast(children()); } diff --git a/app/node/node.h b/app/node/node.h index 423f3c421..ade4bfe22 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -37,11 +37,21 @@ public: virtual QString Category(); virtual QString Description(); + /** + * @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and + * requires re-rendering + * + * Override this if your Node subclass keeps a cache, but call this base function at the end of the subclass function. + * Default behavior is to relay this signal to all connected outputs, which will need to be done as to not break + * the DAG. Even if the time needs to be transformed somehow (e.g. converting media time to sequence time), you can + * call this function with transformed time and relay the signal that way. + */ virtual void InvalidateCache(const rational& start_range, const rational& end_range); - using ParamList = QList; - - ParamList Parameters(); + /** + * @brief Return a list of NodeParams + */ + QList parameters(); public slots: virtual void Process(const rational& time) = 0; diff --git a/app/node/param.cpp b/app/node/param.cpp index 773a73ad8..56aa191ca 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -45,6 +45,11 @@ Node *NodeParam::parent() return static_cast(QObject::parent()); } +const QVector &NodeParam::edges() +{ + return edges_; +} + bool NodeParam::AreDataTypesCompatible(const NodeParam::DataType &output_type, const NodeParam::DataType &input_type) { if (input_type == output_type) { diff --git a/app/node/param.h b/app/node/param.h index c142c1bef..b83c916d5 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -62,6 +62,8 @@ public: Node* parent(); + const QVector& edges(); + static bool AreDataTypesCompatible(const DataType& output_type, const DataType& input_type); static bool AreDataTypesCompatible(const DataType& output_type, const QList& input_types); diff --git a/app/qobjectlistcast.h b/app/qobjectlistcast.h new file mode 100644 index 000000000..5a4660b62 --- /dev/null +++ b/app/qobjectlistcast.h @@ -0,0 +1,56 @@ +#ifndef QOBJECTLISTCAST_H +#define QOBJECTLISTCAST_H + +#include + +template +/** + * @brief Statically cast a QObjectList (aka QList) to a QList of any type (must be a QObject derivative) + * + * Best used to convert the list from a QObject's children() list to a list of another type, assuming it's known that + * all children are of a certain type. + * + * As a static cast, the objects in the QObjectList must all be of the SAME type or the behavior + * is undefined. + */ +QList static_qobjectlist_cast(const QObjectList& list) { + QList new_list; + + new_list.reserve(list.size()); + + for (int i=0;i(list.at(i))); + } + + return new_list; +} + +template +/** + * @brief Dynamically cast a QObjectList (aka QList) to a QList of any type (must be a QObject derivative) + * + * Best used to convert the list from a QObject's children() list to a list of another type, assuming it's known that + * all children are of a certain type. + * + * Unlike static_qobjectlist_cast(), not all of the objects must be of the same type. Objects that are of a different + * type are not added to the list. Therefore it is guarantee the list will contain valid objects of the type you + * specify (or an empty list if there are none). + */ +QList dynamic_qobjectlist_cast(const QObjectList& list) { + QList new_list; + + new_list.reserve(list.size()); + + for (int i=0;i(list.at(i)); + + // Test cast (casted_obj will be nullptr if the dynamic_cast fails) + if (casted_obj != nullptr) { + new_list.append(casted_obj); + } + } + + return new_list; +} + +#endif // QOBJECTLISTCAST_H diff --git a/app/ui/icons/icons.cpp b/app/ui/icons/icons.cpp index 59bf96ecc..bdbed2a7f 100644 --- a/app/ui/icons/icons.cpp +++ b/app/ui/icons/icons.cpp @@ -58,6 +58,10 @@ QIcon olive::icon::Sequence; QIcon olive::icon::Video; QIcon olive::icon::Audio; QIcon olive::icon::Image; +QIcon olive::icon::TriUp; +QIcon olive::icon::TriLeft; +QIcon olive::icon::TriDown; +QIcon olive::icon::TriRight; QIcon olive::icon::Snapping; QIcon olive::icon::ZoomIn; QIcon olive::icon::ZoomOut; @@ -100,6 +104,11 @@ void olive::icon::LoadAll(const QString& theme) Audio = Create(theme, "audiosource"); Image = Create(theme, "imagesource"); + TriUp = Create(theme, "tri-up"); + TriLeft = Create(theme, "tri-left"); + TriDown = Create(theme, "tri-down"); + TriRight = Create(theme, "tri-right"); + Snapping = Create(theme, "magnet"); ZoomIn = Create(theme, "zoomin"); ZoomOut = Create(theme, "zoomout"); diff --git a/app/ui/icons/icons.h b/app/ui/icons/icons.h index bef47c9cd..4ecf5a621 100644 --- a/app/ui/icons/icons.h +++ b/app/ui/icons/icons.h @@ -62,6 +62,12 @@ extern QIcon Video; extern QIcon Audio; extern QIcon Image; +// Triangle Arrows +extern QIcon TriUp; +extern QIcon TriLeft; +extern QIcon TriDown; +extern QIcon TriRight; + // Miscellaneous Icons extern QIcon Snapping; extern QIcon ZoomIn; diff --git a/app/widget/nodeview/CMakeLists.txt b/app/widget/nodeview/CMakeLists.txt index 4d0a86e64..5ec4d2e75 100644 --- a/app/widget/nodeview/CMakeLists.txt +++ b/app/widget/nodeview/CMakeLists.txt @@ -20,5 +20,7 @@ set(OLIVE_SOURCES widget/nodeview/nodeview.cpp widget/nodeview/nodeviewitem.h widget/nodeview/nodeviewitem.cpp + widget/nodeview/nodeviewedge.h + widget/nodeview/nodeviewedge.cpp PARENT_SCOPE ) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 8a8fbfafd..dc547a8ec 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -20,22 +20,81 @@ #include "nodeview.h" -#include - -#include "nodeviewitem.h" - NodeView::NodeView(QWidget *parent) : QGraphicsView(parent), graph_(nullptr) { setScene(&scene_); - NodeViewItem* item = new NodeViewItem(); - - scene_.addItem(item); + connect(&scene_, SIGNAL(changed(const QList&)), this, SLOT(ItemsChanged())); } void NodeView::SetGraph(NodeGraph *graph) { + // Clear the scene of all UI objects + scene_.clear(); + edges_.clear(); + + // Set reference to the graph graph_ = graph; + + // If the graph is valid, add UI objects for each of its Nodes + if (graph_ != nullptr) { + QList graph_nodes = graph_->nodes(); + + foreach (Node* node, graph_nodes) { + NodeViewItem* item = new NodeViewItem(); + + item->SetNode(node); + + scene_.addItem(item); + + // Add a NodeViewEdge for each connection + QList node_params = node->parameters(); + + foreach (NodeParam* param, node_params) { + + // We only bother working with outputs since eventually this will cover all inputs too + // (covering both would lead to duplicates since every edge connects to one input and one output) + if (param->type() == NodeParam::kOutput) { + const QVector& edges = param->edges(); + + foreach(NodeEdgePtr edge, edges) { + NodeViewEdge* edge_ui = new NodeViewEdge(); + + edge_ui->SetEdge(edge); + + scene_.addItem(edge_ui); + + // Keep track of edge widgets so they can be quickly updated whenever nodes move + edges_.append(edge_ui); + } + } + } + } + } +} + +NodeViewItem *NodeView::NodeToUIObject(QGraphicsScene *scene, Node *n) +{ + QList graphics_items = scene->items(); + + for (int i=0;i(graphics_items.at(i)); + + if (item != nullptr) { + if (item->node() == n) { + return item; + } + } + } + + return nullptr; +} + +void NodeView::ItemsChanged() +{ + foreach (NodeViewEdge* edge, edges_) { + edge->Adjust(); + } } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index aa5f19098..b198b31fa 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -24,6 +24,8 @@ #include #include "node/graph.h" +#include "widget/nodeview/nodeviewedge.h" +#include "widget/nodeview/nodeviewitem.h" class NodeView : public QGraphicsView { @@ -33,10 +35,18 @@ public: void SetGraph(NodeGraph* graph); + static NodeViewItem* NodeToUIObject(QGraphicsScene* scene, Node* n); + private: NodeGraph* graph_; QGraphicsScene scene_; + + QList edges_; + +private slots: + void ItemsChanged(); + }; #endif // NODEVIEW_H diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp new file mode 100644 index 000000000..ac4994571 --- /dev/null +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -0,0 +1,72 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "nodeviewedge.h" + +#include + +#include "clamp.h" +#include "lerp.h" +#include "nodeview.h" +#include "nodeviewitem.h" + +NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) : + QGraphicsLineItem(parent), + edge_(nullptr) +{ + // FIXME: This should probably be set to the text color in order to work on light themes + setPen(QPen(Qt::white, 2)); + + // Ensures this UI object is drawn behind other objects + setZValue(-1); +} + +void NodeViewEdge::SetEdge(NodeEdgePtr edge) +{ + // Set the new edge pointer + edge_ = edge; + + // Re-adjust the line positioning for this new edge + Adjust(); +} + +void NodeViewEdge::Adjust() +{ + if (edge_ == nullptr || scene() == nullptr) { + return; + } + + // Get the UI objects of both nodes that this edge connects + NodeViewItem* output = NodeView::NodeToUIObject(scene(), edge_->output()->parent()); + NodeViewItem* input = NodeView::NodeToUIObject(scene(), edge_->input()->parent()); + + // Calculate output/input points + qreal value = clamp(0.5 + (output->pos().y() - input->pos().y()) / (input->rect().height()) / 4, 0.0, 1.0); + + // Use a lerp function to draw the line between the two corners + qreal output_point = output->pos().y() + lerp(0.0, output->rect().height(), 1.0 - value); + qreal input_point = input->pos().y() + lerp(0.0, input->rect().height(), value); + + // Draw a line between the two + setLine(QLineF( + QPointF(output->pos().x() + output->rect().width(), output_point), + QPointF(input->pos().x(), input_point) + )); +} diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h new file mode 100644 index 000000000..7aed55c9a --- /dev/null +++ b/app/widget/nodeview/nodeviewedge.h @@ -0,0 +1,41 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEEDGEITEM_H +#define NODEEDGEITEM_H + +#include + +#include "node/edge.h" + +class NodeViewEdge : public QGraphicsLineItem +{ +public: + NodeViewEdge(QGraphicsItem* parent = nullptr); + + void SetEdge(NodeEdgePtr edge); + + void Adjust(); + +private: + NodeEdgePtr edge_; +}; + +#endif // NODEEDGEITEM_H diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 887585b7e..acc88fc3f 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -20,41 +20,66 @@ #include "nodeviewitem.h" -#include -#include #include #include -#include #include +#include "ui/icons/icons.h" + const int kNodeViewItemBorderWidth = 2; const int kNodeViewItemWidth = 250; -const int kNodeViewItemPadding = 5; +const int kNodeViewItemTextPadding = 4; +const int kNodeViewItemIconPadding = 12; NodeViewItem::NodeViewItem(QGraphicsItem *parent) : QGraphicsRectItem(parent), node_(nullptr) { + // Set flags for this widget setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemIsSelectable); + // Use the current default font height to size this widget QFont f; QFontMetrics fm(f); - setRect(0, 0, kNodeViewItemWidth, fm.height() + kNodeViewItemPadding * 2); + setRect(0, 0, kNodeViewItemWidth, fm.height() + kNodeViewItemTextPadding * 2); + + // FIXME: Magic "number"/magic "color" - allow this to be editable by the user + SetColor(QColor(32, 32, 128)); +} + +void NodeViewItem::SetColor(const QColor &color) +{ + color_ = color; + + // Create a light gradient based on this color + UpdateGradient(); + + update(); +} + +void NodeViewItem::SetNode(Node *n) +{ + node_ = n; + + update(); +} + +Node *NodeViewItem::node() +{ + return node_; } void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - Q_UNUSED(widget) - // Set up border, which will change color if selected QPen pen; pen.setWidth(kNodeViewItemBorderWidth); if (option->state & QStyle::State_Selected) { - pen.setColor(qApp->palette().highlight().color()); + pen.setColor(widget->palette().highlight().color()); } else { // FIXME: Not configurable? pen.setColor(Qt::black); @@ -62,16 +87,37 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti // Draw rect painter->setPen(pen); - painter->setBrush(qApp->palette().window()); + painter->setBrush(brush()); painter->drawRect(rect()); - // Draw text if (node_ != nullptr) { - painter->setPen(qApp->palette().text().color()); + // FIXME: The text is always drawn white assuming the color will be dark - the intention is to provide preset + // colors that will always be dark for the user to choose, so this value can stay white. + painter->setPen(Qt::white); + // Draw the expand icon + QRectF icon_rect = rect(); + icon_rect.adjust(kNodeViewItemIconPadding, + kNodeViewItemIconPadding, + -kNodeViewItemIconPadding, + -kNodeViewItemIconPadding); + olive::icon::TriRight.paint(painter, icon_rect.toRect(), Qt::AlignLeft | Qt::AlignVCenter); + + // Draw the text in a rect (the rect is sized around text already in the constructor) QRectF text_rect = rect(); - text_rect.adjust(kNodeViewItemPadding, kNodeViewItemPadding, -kNodeViewItemPadding, -kNodeViewItemPadding); + text_rect.adjust(kNodeViewItemIconPadding + icon_rect.height() + kNodeViewItemTextPadding, + kNodeViewItemTextPadding, + -kNodeViewItemTextPadding, + -kNodeViewItemTextPadding); painter->drawText(text_rect, Qt::AlignTop | Qt::AlignLeft, node_->Name()); } } + +void NodeViewItem::UpdateGradient() +{ + QLinearGradient grad(QPointF(0, rect().top()), QPointF(0, rect().bottom())); + grad.setColorAt(0, color_.lighter(175)); + grad.setColorAt(1, color_); + setBrush(grad); +} diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 86a752d07..eefac74b6 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -22,6 +22,7 @@ #define NODEVIEWITEM_H #include +#include #include "node/node.h" @@ -30,11 +31,20 @@ class NodeViewItem : public QGraphicsRectItem public: NodeViewItem(QGraphicsItem* parent = nullptr); + void SetColor(const QColor& color); + + void SetNode(Node* n); + Node* node(); + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; private: + void UpdateGradient(); + Node* node_; + + QColor color_; }; #endif // NODEVIEWITEM_H