Add line routing patches
This commit is contained in:
parent
5bb41373b2
commit
65cd227f46
3 changed files with 161 additions and 25 deletions
|
|
@ -1,8 +1,10 @@
|
|||
#include "SquareConnectionPainter.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>
|
||||
|
|
@ -12,51 +14,164 @@
|
|||
#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 out = cgo.endPoint(QtNodes::PortType::Out);
|
||||
QPointF in = cgo.endPoint(QtNodes::PortType::In);
|
||||
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 cId = cgo.connectionId();
|
||||
double spread = static_cast<double>(cId.outPortIndex) * kSpacing;
|
||||
auto const cId = cgo.connectionId();
|
||||
double const spread = static_cast<double>(cId.outPortIndex) * kSpacing;
|
||||
|
||||
double dy = in.y() - out.y();
|
||||
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;
|
||||
}
|
||||
|
||||
double midX = (out.x() + in.x()) / 2.0 + spread;
|
||||
midX = std::max(midX, out.x() + kMinStub);
|
||||
if (in.x() > out.x())
|
||||
// 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);
|
||||
|
||||
double r = std::min({kRadius, std::abs(dy) / 2.0,
|
||||
std::abs(midX - out.x()),
|
||||
std::abs(in.x() - midX)});
|
||||
r = std::max(r, 0.0);
|
||||
QPointF const c1(midX, out.y());
|
||||
QPointF const c2(midX, in.y());
|
||||
|
||||
double sy = (dy > 0) ? 1.0 : -1.0;
|
||||
double hDir = (in.x() >= midX) ? 1.0 : -1.0;
|
||||
|
||||
QPainterPath path(out);
|
||||
|
||||
if (r > 0.5) {
|
||||
path.lineTo(midX - r, out.y());
|
||||
path.quadTo(QPointF(midX, out.y()), QPointF(midX, out.y() + sy * r));
|
||||
path.lineTo(midX, in.y() - sy * r);
|
||||
path.quadTo(QPointF(midX, in.y()), QPointF(midX + hDir * r, in.y()));
|
||||
} else {
|
||||
path.lineTo(midX, out.y());
|
||||
path.lineTo(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 rightX = out.x() + kMinStub + spread;
|
||||
double leftX = in.x() - kMinStub - spread;
|
||||
double midY = (out.y() + in.y()) / 2.0;
|
||||
|
||||
// Use actual node geometry when available so the path routes cleanly
|
||||
// around both nodes instead of cutting through them.
|
||||
auto *scene = cgo.nodeScene();
|
||||
if (scene) {
|
||||
auto *outNGO = scene->nodeGraphicsObject(cId.outNodeId);
|
||||
auto *inNGO = scene->nodeGraphicsObject(cId.inNodeId);
|
||||
if (outNGO && inNGO) {
|
||||
// Map node scene bounds into the CGO's local coordinate space
|
||||
// (endPoint() values live there too).
|
||||
QRectF const outRect =
|
||||
cgo.mapRectFromScene(outNGO->sceneBoundingRect());
|
||||
QRectF const inRect =
|
||||
cgo.mapRectFromScene(inNGO->sceneBoundingRect());
|
||||
|
||||
// Push vertical rails outside both nodes.
|
||||
double const rightEdge = std::max(outRect.right(), inRect.right());
|
||||
rightX = std::max(rightX, rightEdge + kNodePad + spread);
|
||||
|
||||
double const leftEdge = std::min(outRect.left(), inRect.left());
|
||||
leftX = std::min(leftX, leftEdge - kNodePad - spread);
|
||||
|
||||
// Place the horizontal crossover in the gap between nodes when
|
||||
// they don't overlap vertically; otherwise route above or below.
|
||||
double const topInner =
|
||||
std::min(outRect.bottom(), inRect.bottom());
|
||||
double const botInner =
|
||||
std::max(outRect.top(), inRect.top());
|
||||
|
||||
if (botInner > topInner + 2.0 * kNodePad) {
|
||||
// Vertical gap exists — clamp midY into the gap.
|
||||
midY = std::clamp(midY, topInner + kNodePad,
|
||||
botInner - kNodePad);
|
||||
} else {
|
||||
// Nodes overlap vertically — pick the shorter detour.
|
||||
double const above =
|
||||
std::min(outRect.top(), inRect.top()) - kNodePad;
|
||||
double const below =
|
||||
std::max(outRect.bottom(), inRect.bottom()) + kNodePad;
|
||||
midY = (std::abs(midY - above) < std::abs(midY - below))
|
||||
? above
|
||||
: below;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue