424 lines
13 KiB
C++
424 lines
13 KiB
C++
#include "nodeobject.h"
|
|
using Xybrid::UI::NodeObject;
|
|
using Xybrid::UI::PortObject;
|
|
using Xybrid::UI::PortConnectionObject;
|
|
using Xybrid::Data::Node;
|
|
using Xybrid::Data::Port;
|
|
|
|
#include <cmath>
|
|
|
|
#include <QDebug>
|
|
#include <QTimer>
|
|
#include <QPainter>
|
|
#include <QGraphicsScene>
|
|
#include <QStyleOptionGraphicsItem>
|
|
#include <QGraphicsSceneHoverEvent>
|
|
#include <QTextDocument>
|
|
#include <QTextCharFormat>
|
|
#include <QTextCursor>
|
|
#include <QMenu>
|
|
#include <QMessageBox>
|
|
#include <QInputDialog>
|
|
|
|
#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<Data::Port>& 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<PortObject*>(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<Data::Node>& 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<NodeObject> 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<int>(x());
|
|
node->y = static_cast<int>(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<QGraphicsObject*>(this->out->parentItem()->parentItem());
|
|
auto* ip = static_cast<QGraphicsObject*>(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;
|
|
}
|