282 lines
9.1 KiB
C++
282 lines
9.1 KiB
C++
#include "SquareConnectionPainter.h"
|
|
#include "WarpGraphModel.h"
|
|
|
|
#include <QtNodes/internal/BasicGraphicsScene.hpp>
|
|
#include <QtNodes/internal/ConnectionGraphicsObject.hpp>
|
|
#include <QtNodes/internal/ConnectionState.hpp>
|
|
#include <QtNodes/internal/Definitions.hpp>
|
|
#include <QtNodes/internal/NodeGraphicsObject.hpp>
|
|
#include <QtNodes/StyleCollection>
|
|
|
|
#include <QPainter>
|
|
#include <QPainterPath>
|
|
#include <QPainterPathStroker>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
namespace {
|
|
|
|
/// Append a quadratic-rounded right-angle turn to @p path.
|
|
/// Draws a straight segment toward @p corner, a smooth quarter-turn, and
|
|
/// leaves the current position just past the turn headed toward @p after.
|
|
/// The radius is clamped so it never overshoots either the incoming or
|
|
/// outgoing segment.
|
|
void roundedCorner(QPainterPath &path,
|
|
QPointF const &corner,
|
|
QPointF const &after,
|
|
double maxR) {
|
|
QPointF const before = path.currentPosition();
|
|
|
|
double const dx1 = corner.x() - before.x();
|
|
double const dy1 = corner.y() - before.y();
|
|
double const len1 = std::sqrt(dx1 * dx1 + dy1 * dy1);
|
|
|
|
double const dx2 = after.x() - corner.x();
|
|
double const dy2 = after.y() - corner.y();
|
|
double const len2 = std::sqrt(dx2 * dx2 + dy2 * dy2);
|
|
|
|
double const r = std::min({maxR, len1 / 2.0, len2 / 2.0});
|
|
|
|
if (r < 0.5 || len1 < 0.5 || len2 < 0.5) {
|
|
path.lineTo(corner);
|
|
return;
|
|
}
|
|
|
|
double const ux1 = dx1 / len1, uy1 = dy1 / len1;
|
|
double const ux2 = dx2 / len2, uy2 = dy2 / len2;
|
|
|
|
QPointF const start(corner.x() - ux1 * r, corner.y() - uy1 * r);
|
|
QPointF const end(corner.x() + ux2 * r, corner.y() + uy2 * r);
|
|
|
|
path.lineTo(start);
|
|
path.quadTo(corner, end);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
QPainterPath SquareConnectionPainter::orthogonalPath(
|
|
QtNodes::ConnectionGraphicsObject const &cgo) const {
|
|
QPointF const out = cgo.endPoint(QtNodes::PortType::Out);
|
|
QPointF const in = cgo.endPoint(QtNodes::PortType::In);
|
|
|
|
constexpr double kRadius = 5.0;
|
|
constexpr double kSpacing = 8.0;
|
|
constexpr double kMinStub = 20.0;
|
|
constexpr double kNodePad = 15.0;
|
|
|
|
auto const cId = cgo.connectionId();
|
|
auto *sceneForChannel = cgo.nodeScene();
|
|
auto *mdl = sceneForChannel
|
|
? dynamic_cast<WarpGraphModel *>(&sceneForChannel->graphModel())
|
|
: nullptr;
|
|
bool connectionAlive = mdl ? mdl->connectionExists(cId) : true;
|
|
|
|
double spread = static_cast<double>(cId.outPortIndex) * kSpacing;
|
|
if (mdl && connectionAlive) {
|
|
auto ch = mdl->connectionChannel(cId);
|
|
spread = (static_cast<double>(ch.index) - (ch.count - 1) / 2.0)
|
|
* kSpacing;
|
|
}
|
|
|
|
double const dy = in.y() - out.y();
|
|
|
|
// Straight line when ports are nearly level and input is to the right.
|
|
if (std::abs(dy) < 0.5 && in.x() > out.x()) {
|
|
QPainterPath path(out);
|
|
path.lineTo(in);
|
|
return path;
|
|
}
|
|
|
|
// A connection is "backward" when the input port is close to or left of
|
|
// the output port — the simple 3-segment Z-path would double back on
|
|
// itself and cut through nodes. Use a 5-segment S-shaped path instead.
|
|
bool const backward = (in.x() < out.x() + 2.0 * kMinStub);
|
|
|
|
if (!backward) {
|
|
// ---- Forward: 3-segment Z-path ----
|
|
double midX = (out.x() + in.x()) / 2.0 + spread;
|
|
midX = std::max(midX, out.x() + kMinStub);
|
|
midX = std::min(midX, in.x() - kMinStub);
|
|
|
|
QPointF const c1(midX, out.y());
|
|
QPointF const c2(midX, in.y());
|
|
|
|
QPainterPath path(out);
|
|
roundedCorner(path, c1, c2, kRadius);
|
|
roundedCorner(path, c2, in, kRadius);
|
|
path.lineTo(in);
|
|
return path;
|
|
}
|
|
|
|
// ---- Backward: 5-segment S-path ----
|
|
//
|
|
// The path leaves the output port going right, routes around both
|
|
// nodes via two vertical rails (one on the right, one on the left),
|
|
// connected by a horizontal crossover, then enters the input port
|
|
// from the left:
|
|
//
|
|
// leftX rightX
|
|
// | |
|
|
// in ------+ | seg 5 + corner 4
|
|
// | | seg 4
|
|
// +------- midY --+ seg 3 + corners 2,3
|
|
// | seg 2
|
|
// out -------+ seg 1 + corner 1
|
|
//
|
|
|
|
double railOffset = 0.0;
|
|
if (mdl && connectionAlive) {
|
|
auto ch = mdl->connectionChannel(cId);
|
|
railOffset = static_cast<double>(ch.index) * kSpacing;
|
|
}
|
|
|
|
double rightX = out.x() + kMinStub + railOffset;
|
|
double leftX = in.x() - kMinStub - railOffset;
|
|
double midY = (out.y() + in.y()) / 2.0 + spread;
|
|
|
|
auto *scene = cgo.nodeScene();
|
|
if (scene) {
|
|
auto *outNGO = scene->nodeGraphicsObject(cId.outNodeId);
|
|
auto *inNGO = scene->nodeGraphicsObject(cId.inNodeId);
|
|
if (outNGO && inNGO) {
|
|
QRectF const outRect =
|
|
cgo.mapRectFromScene(outNGO->sceneBoundingRect());
|
|
QRectF const inRect =
|
|
cgo.mapRectFromScene(inNGO->sceneBoundingRect());
|
|
|
|
double const rightEdge = std::max(outRect.right(), inRect.right());
|
|
rightX = std::max(rightX, rightEdge + kNodePad + railOffset);
|
|
|
|
double const leftEdge = std::min(outRect.left(), inRect.left());
|
|
leftX = std::min(leftX, leftEdge - kNodePad - railOffset);
|
|
|
|
double const topInner =
|
|
std::min(outRect.bottom(), inRect.bottom());
|
|
double const botInner =
|
|
std::max(outRect.top(), inRect.top());
|
|
|
|
if (botInner > topInner + 2.0 * kNodePad) {
|
|
midY = std::clamp(midY, topInner + kNodePad,
|
|
botInner - kNodePad);
|
|
} else {
|
|
double const above =
|
|
std::min(outRect.top(), inRect.top()) - kNodePad;
|
|
double const below =
|
|
std::max(outRect.bottom(), inRect.bottom()) + kNodePad;
|
|
double baseMidY = (out.y() + in.y()) / 2.0;
|
|
midY = (std::abs(baseMidY - above) < std::abs(baseMidY - below))
|
|
? above + spread
|
|
: below + spread;
|
|
}
|
|
}
|
|
}
|
|
|
|
QPointF const p1(rightX, out.y());
|
|
QPointF const p2(rightX, midY);
|
|
QPointF const p3(leftX, midY);
|
|
QPointF const p4(leftX, in.y());
|
|
|
|
QPainterPath path(out);
|
|
roundedCorner(path, p1, p2, kRadius);
|
|
roundedCorner(path, p2, p3, kRadius);
|
|
roundedCorner(path, p3, p4, kRadius);
|
|
roundedCorner(path, p4, in, kRadius);
|
|
path.lineTo(in);
|
|
return path;
|
|
}
|
|
|
|
void SquareConnectionPainter::paint(
|
|
QPainter *painter,
|
|
QtNodes::ConnectionGraphicsObject const &cgo) const {
|
|
auto const &style = QtNodes::StyleCollection::connectionStyle();
|
|
|
|
bool const hovered = cgo.connectionState().hovered();
|
|
bool const selected = cgo.isSelected();
|
|
bool const sketch = cgo.connectionState().requiresPort();
|
|
|
|
auto path = orthogonalPath(cgo);
|
|
|
|
float peakLevel = 0.0f;
|
|
auto *scene = cgo.nodeScene();
|
|
if (scene) {
|
|
auto *model = dynamic_cast<WarpGraphModel *>(&scene->graphModel());
|
|
if (model) {
|
|
auto cId = cgo.connectionId();
|
|
if (model->connectionExists(cId)) {
|
|
peakLevel = model->nodePeakLevel(cId.outNodeId);
|
|
|
|
if (peakLevel < 0.005f) {
|
|
auto const *outData = model->warpNodeData(cId.outNodeId);
|
|
if (outData &&
|
|
WarpGraphModel::classifyNode(outData->info) ==
|
|
WarpNodeType::kApplication) {
|
|
peakLevel = std::max(peakLevel,
|
|
model->nodePeakLevel(cId.inNodeId));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto activeColor = [&](QColor base) -> QColor {
|
|
if (peakLevel < 0.005f)
|
|
return base;
|
|
float t = std::min(peakLevel * 2.0f, 1.0f);
|
|
int r = static_cast<int>(base.red() + t * (60 - base.red()));
|
|
int g = static_cast<int>(base.green() + t * (210 - base.green()));
|
|
int b = static_cast<int>(base.blue() + t * (80 - base.blue()));
|
|
return QColor(std::clamp(r, 0, 255),
|
|
std::clamp(g, 0, 255),
|
|
std::clamp(b, 0, 255),
|
|
base.alpha());
|
|
};
|
|
|
|
if (hovered || selected) {
|
|
QPen pen;
|
|
pen.setWidth(static_cast<int>(2 * style.lineWidth()));
|
|
pen.setColor(selected ? style.selectedHaloColor() : style.hoveredColor());
|
|
painter->setPen(pen);
|
|
painter->setBrush(Qt::NoBrush);
|
|
painter->drawPath(path);
|
|
}
|
|
|
|
if (sketch) {
|
|
QPen pen;
|
|
pen.setWidth(static_cast<int>(style.constructionLineWidth()));
|
|
pen.setColor(style.constructionColor());
|
|
pen.setStyle(Qt::DashLine);
|
|
painter->setPen(pen);
|
|
painter->setBrush(Qt::NoBrush);
|
|
painter->drawPath(path);
|
|
} else {
|
|
QColor base = selected ? style.selectedColor() : style.normalColor();
|
|
QColor color = selected ? base : activeColor(base);
|
|
float width = style.lineWidth();
|
|
if (!selected && peakLevel > 0.005f)
|
|
width += peakLevel * 1.5f;
|
|
|
|
QPen pen;
|
|
pen.setWidthF(width);
|
|
pen.setColor(color);
|
|
painter->setPen(pen);
|
|
painter->setBrush(Qt::NoBrush);
|
|
painter->drawPath(path);
|
|
}
|
|
|
|
double const pointRadius = style.pointDiameter() / 2.0;
|
|
painter->setPen(style.constructionColor());
|
|
painter->setBrush(style.constructionColor());
|
|
painter->drawEllipse(cgo.out(), pointRadius, pointRadius);
|
|
painter->drawEllipse(cgo.in(), pointRadius, pointRadius);
|
|
}
|
|
|
|
QPainterPath SquareConnectionPainter::getPainterStroke(
|
|
QtNodes::ConnectionGraphicsObject const &cgo) const {
|
|
auto path = orthogonalPath(cgo);
|
|
|
|
QPainterPathStroker stroker;
|
|
stroker.setWidth(10.0);
|
|
|
|
return stroker.createStroke(path);
|
|
}
|