Add line routing patches
This commit is contained in:
parent
5bb41373b2
commit
65cd227f46
3 changed files with 161 additions and 25 deletions
|
|
@ -71,10 +71,12 @@ if(WARPPIPE_BUILD_GUI)
|
||||||
|
|
||||||
find_package(Qt6 6.2 REQUIRED COMPONENTS Core Widgets)
|
find_package(Qt6 6.2 REQUIRED COMPONENTS Core Widgets)
|
||||||
|
|
||||||
|
set(_qtnodes_patch ${CMAKE_CURRENT_SOURCE_DIR}/patches/qtnodes-connection-boundingRect.patch)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
QtNodes
|
QtNodes
|
||||||
GIT_REPOSITORY https://github.com/paceholder/nodeeditor
|
GIT_REPOSITORY https://github.com/paceholder/nodeeditor
|
||||||
GIT_TAG master
|
GIT_TAG master
|
||||||
|
PATCH_COMMAND sh -c "git apply ${_qtnodes_patch} || true"
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(QtNodes)
|
FetchContent_MakeAvailable(QtNodes)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
#include "SquareConnectionPainter.h"
|
#include "SquareConnectionPainter.h"
|
||||||
|
|
||||||
|
#include <QtNodes/internal/BasicGraphicsScene.hpp>
|
||||||
#include <QtNodes/internal/ConnectionGraphicsObject.hpp>
|
#include <QtNodes/internal/ConnectionGraphicsObject.hpp>
|
||||||
#include <QtNodes/internal/ConnectionState.hpp>
|
#include <QtNodes/internal/ConnectionState.hpp>
|
||||||
#include <QtNodes/internal/Definitions.hpp>
|
#include <QtNodes/internal/Definitions.hpp>
|
||||||
|
#include <QtNodes/internal/NodeGraphicsObject.hpp>
|
||||||
#include <QtNodes/StyleCollection>
|
#include <QtNodes/StyleCollection>
|
||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
@ -12,51 +14,164 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#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(
|
QPainterPath SquareConnectionPainter::orthogonalPath(
|
||||||
QtNodes::ConnectionGraphicsObject const &cgo) const {
|
QtNodes::ConnectionGraphicsObject const &cgo) const {
|
||||||
QPointF out = cgo.endPoint(QtNodes::PortType::Out);
|
QPointF const out = cgo.endPoint(QtNodes::PortType::Out);
|
||||||
QPointF in = cgo.endPoint(QtNodes::PortType::In);
|
QPointF const in = cgo.endPoint(QtNodes::PortType::In);
|
||||||
|
|
||||||
constexpr double kRadius = 5.0;
|
constexpr double kRadius = 5.0;
|
||||||
constexpr double kSpacing = 8.0;
|
constexpr double kSpacing = 8.0;
|
||||||
constexpr double kMinStub = 20.0;
|
constexpr double kMinStub = 20.0;
|
||||||
|
constexpr double kNodePad = 15.0;
|
||||||
|
|
||||||
auto cId = cgo.connectionId();
|
auto const cId = cgo.connectionId();
|
||||||
double spread = static_cast<double>(cId.outPortIndex) * kSpacing;
|
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()) {
|
if (std::abs(dy) < 0.5 && in.x() > out.x()) {
|
||||||
QPainterPath path(out);
|
QPainterPath path(out);
|
||||||
path.lineTo(in);
|
path.lineTo(in);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
double midX = (out.x() + in.x()) / 2.0 + spread;
|
// A connection is "backward" when the input port is close to or left of
|
||||||
midX = std::max(midX, out.x() + kMinStub);
|
// the output port — the simple 3-segment Z-path would double back on
|
||||||
if (in.x() > out.x())
|
// 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);
|
midX = std::min(midX, in.x() - kMinStub);
|
||||||
|
|
||||||
double r = std::min({kRadius, std::abs(dy) / 2.0,
|
QPointF const c1(midX, out.y());
|
||||||
std::abs(midX - out.x()),
|
QPointF const c2(midX, in.y());
|
||||||
std::abs(in.x() - midX)});
|
|
||||||
r = std::max(r, 0.0);
|
|
||||||
|
|
||||||
double sy = (dy > 0) ? 1.0 : -1.0;
|
QPainterPath path(out);
|
||||||
double hDir = (in.x() >= midX) ? 1.0 : -1.0;
|
roundedCorner(path, c1, c2, kRadius);
|
||||||
|
roundedCorner(path, c2, in, kRadius);
|
||||||
QPainterPath path(out);
|
path.lineTo(in);
|
||||||
|
return path;
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 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);
|
path.lineTo(in);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
patches/qtnodes-connection-boundingRect.patch
Normal file
19
patches/qtnodes-connection-boundingRect.patch
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
diff --git a/src/ConnectionGraphicsObject.cpp b/src/ConnectionGraphicsObject.cpp
|
||||||
|
index 05ae46b..9a1eeea 100644
|
||||||
|
--- a/src/ConnectionGraphicsObject.cpp
|
||||||
|
+++ b/src/ConnectionGraphicsObject.cpp
|
||||||
|
@@ -107,6 +107,14 @@ QRectF ConnectionGraphicsObject::boundingRect() const
|
||||||
|
|
||||||
|
QRectF commonRect = basicRect.united(c1c2Rect);
|
||||||
|
|
||||||
|
+ // Include the painter stroke so custom painters that route beyond
|
||||||
|
+ // the default bezier control points are not clipped.
|
||||||
|
+ auto *ns = nodeScene();
|
||||||
|
+ if (ns) {
|
||||||
|
+ QRectF strokeRect = ns->connectionPainter().getPainterStroke(*this).boundingRect();
|
||||||
|
+ commonRect = commonRect.united(strokeRect);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
auto const &connectionStyle = StyleCollection::connectionStyle();
|
||||||
|
float const diam = connectionStyle.pointDiameter();
|
||||||
|
QPointF const cornerOffset(diam, diam);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue