#include "nodeobject.h" using Xybrid::UI::NodeObject; using Xybrid::UI::PortObject; using Xybrid::UI::PortConnectionObject; using Xybrid::Data::Node; using Xybrid::Data::Port; #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util/strings.h" namespace { const QColor tcolor[] { QColor(239, 179, 59), // Audio QColor(163, 95, 191), // Command QColor(95, 191, 163), // MIDI QColor(127, 127, 255), // Parameter }; } void PortObject::connectTo(PortObject* o) { if (!o) return; if (connections.find(o) != connections.end()) return; if (port->type == o->port->type) return; PortObject* in; PortObject* out; if (port->type == Port::Input) { in = this; out = o; } else { out = this; in = o; } if (out->port->connect(in->port)) { /*auto* pc =*/ new PortConnectionObject(in, out); } } void PortObject::setHighlighted(bool h, bool hideLabel) { highlighted = h; bool lv = h && !hideLabel; if (lv) { QString txt = QString("%1 %2").arg(Util::enumName(port->dataType()).toLower()).arg(Util::hex(port->index)); if (!port->name.empty()) txt = QString("%1 (%2)").arg(QString::fromStdString(port->name)).arg(txt); QColor c = tcolor[port->dataType()]; label->setText(txt); label->setBrush(c); labelShadow->setText(txt); labelShadow->setBrush(c.darker(400)); labelShadow->setPen(QPen(labelShadow->brush(), 2.5)); auto lbr = label->boundingRect(); if (port->type == Port::Input) label->setPos(QPointF(-lbr.width() - (portSize/2 + portSpacing), lbr.height() * -.5)); else label->setPos(QPointF(portSize/2 + portSpacing, lbr.height() * -.5)); auto lbsr = labelShadow->boundingRect(); labelShadow->setPos(label->pos() + (lbr.bottomRight() - lbsr.bottomRight()) / 2); } label->setVisible(lv); labelShadow->setVisible(lv); update(); } PortObject::PortObject(const std::shared_ptr& p) { port = p; p->obj = this; setAcceptHoverEvents(true); setAcceptedMouseButtons(Qt::LeftButton); setFlag(QGraphicsItem::ItemSendsScenePositionChanges); labelShadow = new QGraphicsSimpleTextItem(this); labelShadow->setVisible(false); label = new QGraphicsSimpleTextItem(this); label->setVisible(false); for (auto c : port->connections) { if (auto cc = c.lock(); cc && cc->obj) connectTo(cc->obj); } setCursor(Qt::CursorShape::CrossCursor); } PortObject::~PortObject() { while (connections.begin() != connections.end()) delete connections.begin()->second; } void PortObject::mousePressEvent(QGraphicsSceneMouseEvent*) { //setCursor(Qt::CursorShape::CrossCursor); setHighlighted(true, true); dragLine.reset(new QGraphicsLineItem()); dragLine->setPen(QPen(tcolor[port->dataType()].lighter(125), 1.5)); dragLine->setLine(QLineF(scenePos(), scenePos())); dragLine->setZValue(100); scene()->addItem(dragLine.get()); } void PortObject::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) { //unsetCursor(); dragLine.reset(); auto* i = scene()->itemAt(e->scenePos(), QTransform()); if (i && i->type() == PortObject::Type) { auto* p = static_cast(i); //qDebug() << "connection:" << port->connect(p->port); connectTo(p); } } void PortObject::mouseMoveEvent(QGraphicsSceneMouseEvent* e) { if (dragLine) dragLine->setLine(QLineF(scenePos(), e->scenePos())); update(); } void PortObject::hoverEnterEvent(QGraphicsSceneHoverEvent*) { setHighlighted(true); } void PortObject::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { setHighlighted(false); } void PortObject::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) { auto* m = new QMenu(); m->addAction("Disconnect All", this, [this] { while (connections.begin() != connections.end()) { auto* c = connections.begin()->second; c->in->port->disconnect(c->out->port); delete c; } });//->setEnabled(this->connections.size() != 0); m->setAttribute(Qt::WA_DeleteOnClose); m->popup(e->screenPos()); } void PortObject::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) { QColor bg = tcolor[port->dataType()]; QColor outline = bg.darker(200); if (highlighted) outline = bg.lighter(150); painter->setRenderHint(QPainter::RenderHint::Antialiasing); painter->setBrush(QBrush(bg)); painter->setPen(QPen(QBrush(outline), 1)); painter->drawEllipse(boundingRect()); } QRectF PortObject::boundingRect() const { return QRectF(portSize * -.5, portSize * -.5, portSize, portSize); } NodeObject::NodeObject(const std::shared_ptr& n) { node = n; node->obj = this; setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemIsFocusable); setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemSendsScenePositionChanges); setPos(node->x, node->y); connect(this, &QGraphicsObject::xChanged, this, &NodeObject::onMoved); connect(this, &QGraphicsObject::yChanged, this, &NodeObject::onMoved); //setToolTip(QString::fromStdString(node->name)); createPorts(); node->onGadgetCreated(); } void NodeObject::setGadgetSize(QPointF p) { gadgetSize_ = p; updateGeometry(); } void NodeObject::promptDelete() { QPointer t = this; if (QMessageBox::warning(nullptr, "Are you sure?", QString("Remove node? (This cannot be undone!)"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return; if (t) { t->node->parentTo(nullptr); // unparent node so it gets deleted delete t; } } void NodeObject::mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) { node->onDoubleClick(); } void NodeObject::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) { if (!isSelected()) { for (auto* s : scene()->selectedItems()) s->setSelected(false); setSelected(true); } auto* m = new QMenu(); if (canRename) { m->addAction("Rename...", this, [this] { bool ok = false; auto cn = node->name.empty() ? QString::fromStdString(node->pluginName()) : QString("\"%1\"").arg(QString::fromStdString(node->name)); auto capt = QString("Rename %1:").arg(cn); auto n = QInputDialog::getText(nullptr, "Rename...", capt, QLineEdit::Normal, QString::fromStdString(node->name), &ok); if (!ok) return; // canceled //setToolTip(n); node->name = n.toStdString(); node->onRename(); }); } m->addAction("Delete node", this, &NodeObject::promptDelete); m->setAttribute(Qt::WA_DeleteOnClose); m->popup(e->screenPos()); } void NodeObject::bringToTop(bool force) { for (auto o : collidingItems()) if (o->parentItem() == parentItem() && (force || !o->isSelected())) o->stackBefore(this); } void NodeObject::createPorts() { auto* ipc = new QGraphicsLineItem(this); auto* opc = new QGraphicsLineItem(this); inputPortContainer.reset(ipc); outputPortContainer.reset(opc); updateGeometry(); QPen p(QColor(95, 95, 95), 2.5); QPointF inc(0, PortObject::portSize + PortObject::portSpacing); QPointF cursor = QPointF(0, 0); for (auto mdt : node->inputs) { for (auto pp : mdt.second) { auto* p = new PortObject(pp.second); p->setParentItem(ipc); p->setPos(cursor); cursor += inc; } } ipc->setVisible(cursor.y() > 0); cursor -= inc; ipc->setLine(QLineF(QPointF(0, 0), cursor)); ipc->setPen(p); cursor = QPointF(0, 0); for (auto mdt : node->outputs) { for (auto pp : mdt.second) { auto* p = new PortObject(pp.second); p->setParentItem(opc); p->setPos(cursor); cursor += inc; } } opc->setVisible(cursor.y() > 0); cursor -= inc; opc->setLine(QLineF(QPointF(0, 0), cursor)); opc->setPen(p); } void NodeObject::updateGeometry() { qreal pm = PortObject::portSize * .5 + PortObject::portSpacing; if (inputPortContainer) inputPortContainer->setPos(QPointF(-pm, PortObject::portSize)); if (outputPortContainer) outputPortContainer->setPos(QPointF(boundingRect().width() + pm, PortObject::portSize)); } void NodeObject::onMoved() { if (x() < 0) setX(0); else setX(std::round(x())); if (y() < 0) setY(0); else setY(std::round(y())); node->x = static_cast(x()); node->y = static_cast(y()); if (isSelected()) { bringToTop(); } if (auto s = scene(); s) s->update(); } void NodeObject::focusInEvent(QFocusEvent *) { bringToTop(true); } void NodeObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt, QWidget *) { if (customChrome) { node->drawCustomChrome(painter, opt); return; } QRectF r = boundingRect(); QColor outline = QColor(31, 31, 31); if (opt->state & QStyle::State_Selected) outline = QColor(127, 127, 255); QLinearGradient fill(QPointF(0, 0), QPointF(0, r.height())); fill.setColorAt(0, QColor(95, 95, 95)); fill.setColorAt(16.0/r.height(), QColor(63, 63, 63)); fill.setColorAt(1.0 - (1.0 - 16.0/r.height()) / 2, QColor(55, 55, 55)); fill.setColorAt(1, QColor(35, 35, 35)); painter->setRenderHint(QPainter::RenderHint::Antialiasing); painter->setBrush(QBrush(fill)); painter->setPen(QPen(QBrush(outline), 2)); painter->drawRoundedRect(r, 8, 8); QRectF tr = r - QMargins(3, 2, 3, 0); if (!node->name.empty()) { painter->setPen(QColor(222, 222, 222)); painter->drawText(tr, Qt::AlignLeft, QString::fromStdString(node->name)); tr -= QMarginsF(0, painter->fontMetrics().height(), 0, 0); } painter->setPen(QColor(171, 171, 171)); painter->drawText(tr, Qt::AlignLeft, QString::fromStdString(node->pluginName())); } QRectF NodeObject::boundingRect() const { if (customChrome) return QRectF(QPointF(0, 0), gadgetSize_); return QRectF(0, 0, 192, 36);// + QMarginsF(8, 8, 8, 8); } PortConnectionObject::PortConnectionObject(PortObject* in, PortObject* out) { this->in = in; this->out = out; // remove dupes if (in->connections[out]) delete in->connections[out]; if (out->connections[in]) delete out->connections[in]; // and hook up in->connections[out] = this; out->connections[in] = this; QTimer::singleShot(1, [this] { this->in->scene()->addItem(this); }); setZValue(-100); setAcceptHoverEvents(true); //setFlag(QGraphicsItem::GraphicsItemFlag::) QTimer::singleShot(1, [this] { auto* op = static_cast(this->out->parentItem()->parentItem()); auto* ip = static_cast(this->in->parentItem()->parentItem()); connect(op, &QGraphicsObject::xChanged, this, &PortConnectionObject::updateEnds); connect(op, &QGraphicsObject::yChanged, this, &PortConnectionObject::updateEnds); connect(ip, &QGraphicsObject::xChanged, this, &PortConnectionObject::updateEnds); connect(ip, &QGraphicsObject::xChanged, this, &PortConnectionObject::updateEnds); updateEnds(); }); } void PortConnectionObject::updateEnds() { setPos((out->scenePos() + in->scenePos()) * .5); update(); } PortConnectionObject::~PortConnectionObject() { in->connections.erase(out); out->connections.erase(in); } void PortConnectionObject::disconnect() { out->port->disconnect(in->port); delete this; } void PortConnectionObject::hoverEnterEvent(QGraphicsSceneHoverEvent*) { highlighted = true; update(); } void PortConnectionObject::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { highlighted = false; update(); } void PortConnectionObject::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) { auto* m = new QMenu(); m->addAction("Disconnect", this, [this] { disconnect(); }); m->popup(e->screenPos()); } void PortConnectionObject::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) { QColor c = tcolor[in->port->dataType()]; if (highlighted) c = c.lighter(150); painter->setPen(Qt::NoPen); painter->setBrush(QBrush(c)); painter->drawPath(shape(2.5)); } QRectF PortConnectionObject::boundingRect() const { return shape().boundingRect().normalized();//controlPointRect().normalized().united(QRectF(mapFromScene(out->scenePos()), mapFromScene(in->scenePos()))); } QPainterPath PortConnectionObject::shape(qreal width) const { QPainterPath path; auto start = mapFromScene(out->scenePos()); auto end = mapFromScene(in->scenePos()); path.moveTo(start); //QPointF mod(std::max(std::max((end.x() - start.x()) * .64, (start.x() - end.x()) * .24), 64.0), 0); QPointF out(0, 0); //path.lineTo(start+out); QPointF mod(std::max((end.x() - start.x()) * .64, 96.0), 0); path.cubicTo(start + out + mod, end - mod, end - out); //path.lineTo(end); if (width <= 0) return path; QPainterPathStroker qp; qp.setWidth(width); qp.setCapStyle(Qt::PenCapStyle::RoundCap); qp.setJoinStyle(Qt::PenJoinStyle::RoundJoin); auto p = qp.createStroke(path); return p; }