aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/qmldesigner
diff options
context:
space:
mode:
authorHenning Gruendl <henning.gruendl@qt.io>2020-05-06 11:18:51 +0200
committerThomas Hartmann <thomas.hartmann@qt.io>2020-05-06 15:34:34 +0000
commitf2ca950d182e33045693de2b4408dcfe8c9a89e3 (patch)
tree6b4c960b39b383c4cd0d8ef12b400597d6f3a5e9 /src/plugins/qmldesigner
parentc5157368bd7b4d252927c4aa146f20fa4229b8a0 (diff)
QmlDesigner: Add bezier curves to transition item
* Add support for bezier curves on transition item * Rework connection drawing Task-number: QDS-2055 Change-Id: Ifbe30df4965eee0f39681a2285cc664aaffda667 Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
Diffstat (limited to 'src/plugins/qmldesigner')
-rw-r--r--src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp295
-rw-r--r--src/plugins/qmldesigner/components/formeditor/formeditoritem.h8
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp12
3 files changed, 224 insertions, 91 deletions
diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
index 42847fa7e7..3dff938e4d 100644
--- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
@@ -833,11 +833,10 @@ static void drawArrow(QPainter *painter,
painter->restore();
}
-static void drawRoundedCorner(QPainter *painter,
- const QPointF &s,
- const QPointF &m,
- const QPointF &e,
- int radius)
+static QPainterPath roundedCorner(const QPointF &s,
+ const QPointF &m,
+ const QPointF &e,
+ int radius)
{
const QVector2D sm(m - s);
const QVector2D me(e - m);
@@ -846,30 +845,185 @@ static void drawRoundedCorner(QPainter *painter,
const int actualRadius = qMin(radius, static_cast<int>(qMin(smLength, meLength)));
const QVector2D smNorm = sm.normalized();
const QVector2D meNorm = me.normalized();
-
- const QPointF arcStartP = s + (smNorm * (smLength - actualRadius)).toPointF();
-
QRectF rect(m, QSizeF(actualRadius * 2, actualRadius * 2));
- painter->drawLine(s, arcStartP);
+ QPainterPath path(s);
- if ((smNorm.y() < 0 && meNorm.x() > 0) || (smNorm.x() < 0 && meNorm.y() > 0)) {
+ if (smNorm.y() < 0 && meNorm.x() > 0) {
+ rect.moveTopLeft(m);
+ path.arcTo(rect, 180, -90);
+ } else if (smNorm.x() < 0 && meNorm.y() > 0) {
rect.moveTopLeft(m);
- painter->drawArc(rect, 90 * 16, 90 * 16);
- } else if ((smNorm.y() > 0 && meNorm.x() > 0) || (smNorm.x() < 0 && meNorm.y() < 0)) {
+ path.arcTo(rect, 90, 90);
+ } else if (smNorm.y() > 0 && meNorm.x() > 0) {
rect.moveBottomLeft(m);
- painter->drawArc(rect, 180 * 16, 90 * 16);
- } else if ((smNorm.x() > 0 && meNorm.y() > 0) || (smNorm.y() < 0 && meNorm.x() < 0)) {
+ path.arcTo(rect, 180, 90);
+ } else if (smNorm.x() < 0 && meNorm.y() < 0) {
+ rect.moveBottomLeft(m);
+ path.arcTo(rect, 270, -90);
+ } else if (smNorm.x() > 0 && meNorm.y() > 0) {
+ rect.moveTopRight(m);
+ path.arcTo(rect, 90, -90);
+ } else if (smNorm.y() < 0 && meNorm.x() < 0) {
rect.moveTopRight(m);
- painter->drawArc(rect, 0 * 16, 90 * 16);
- } else if ((smNorm.y() > 0 && meNorm.x() < 0) || (smNorm.x() > 0 && meNorm.y() < 0)) {
+ path.arcTo(rect, 0, 90);
+ } else if (smNorm.y() > 0 && meNorm.x() < 0) {
+ rect.moveBottomRight(m);
+ path.arcTo(rect, 0, -90);
+ } else if (smNorm.x() > 0 && meNorm.y() < 0) {
rect.moveBottomRight(m);
- painter->drawArc(rect, 270 * 16, 90 * 16);
+ path.arcTo(rect, 270, 90);
}
- const QPointF arcEndP = e - (meNorm * (meLength - actualRadius)).toPointF();
+ path.lineTo(e);
+ return path;
+}
+
+// This function determines whether the vertices are in cw or ccw order.
+// It finds the lowest and rightmost vertex, and computes the cross-product
+// of the vectors along its incident edges.
+// Written by Joseph O'Rourke, 25 August 1995. orourke@cs.smith.edu
+// 1: ccw
+// 0: default
+// -1: cw
+
+static int counterClockWise(const std::vector<QPointF> &points)
+{
+ if (points.empty())
+ return 0;
+
+ // FindLR finds the lowest, rightmost point.
+ auto findLR = [](const std::vector<QPointF> &points) {
+ int i = 0;
+ int m = 0;
+ QPointF min = points.front();
- painter->drawLine(arcEndP, e);
+ for (const auto p : points) {
+ if ((p.y() < min.y()) || ((p.y() == min.y()) && (p.x() > min.x()))) {
+ m = i;
+ min = p;
+ }
+ ++i;
+ }
+ return m;
+ };
+
+ const int m = findLR(points);
+ const int n = points.size();
+
+ // Determine previous and next point to m (the lowest, rightmost point).
+ const QPointF a = points[(m + (n - 1)) % n];
+ const QPointF b = points[m];
+ const QPointF c = points[(m + 1) % n];
+
+ const int area = a.x() * b.y() - a.y() * b.x() +
+ a.y() * c.x() - a.x() * c.y() +
+ b.x() * c.y() - c.x() * b.y();
+
+ if (area > 0)
+ return 1;
+ else if (area < 0)
+ return -1;
+ else
+ return 0;
+}
+
+static QPainterPath quadBezier(const QPointF &s,
+ const QPointF &c,
+ const QPointF &e,
+ int bezier,
+ int breakOffset)
+{
+ QLineF se(s, e);
+ QPointF breakPoint = se.pointAt(breakOffset / 100.0);
+ QLineF breakLine;
+
+ if (counterClockWise({s, c, e}) == 1)
+ breakLine = QLineF(breakPoint, breakPoint + QPointF(se.dy(), -se.dx()));
+ else
+ breakLine = QLineF(breakPoint, breakPoint + QPointF(-se.dy(), se.dx()));
+
+ breakLine.setLength(se.length());
+
+ const QPointF controlPoint = breakLine.pointAt(bezier / 100.0);
+
+ QPainterPath path(s);
+ path.quadTo(controlPoint, e);
+
+ return path;
+}
+
+static QPainterPath cubicBezier(const QPointF &s,
+ const QPointF &c1,
+ const QPointF &c2,
+ const QPointF &e,
+ int bezier)
+{
+ QPainterPath path(s);
+ const QPointF adjustedC1 = QLineF(s, c1).pointAt(bezier / 100.0);
+ const QPointF adjustedC2 = QLineF(e, c2).pointAt(bezier / 100.0);
+
+ path.cubicTo(adjustedC1, adjustedC2, e);
+
+ return path;
+}
+
+
+static QPainterPath lShapedConnection(const QPointF &start,
+ const QPointF &end,
+ Qt::Orientation orientation,
+ const ConnectionStyle &style)
+{
+ const QPointF mid = (orientation == Qt::Horizontal) ? QPointF(end.x(), start.y())
+ : QPointF(start.x(), end.y());
+
+ if (style.type == ConnectionType::Default) {
+ if (style.radius == 0) {
+ QPainterPath path(start);
+ path.lineTo(mid);
+ path.lineTo(end);
+ return path;
+ } else {
+ return roundedCorner(start, mid, end, style.radius);
+ }
+ } else {
+ return quadBezier(start, mid, end, style.bezier, style.breakOffset);
+ }
+}
+
+static QPainterPath sShapedConnection(const QPointF &start,
+ const QPointF &end,
+ Qt::Orientation orientation,
+ const ConnectionStyle &style)
+{
+ const qreal middleFactor = style.breakOffset / 100.0;
+ QPointF mid1;
+ QPointF mid2;
+
+ if (orientation == Qt::Horizontal) {
+ mid1 = QPointF(start.x() * middleFactor + end.x() * (1 - middleFactor), start.y());
+ mid2 = QPointF(mid1.x(), end.y());
+ } else {
+ mid1 = QPointF(start.x(), start.y() * middleFactor + end.y() * (1 - middleFactor));
+ mid2 = QPointF(end.x(), mid1.y());
+ }
+
+ if (style.type == ConnectionType::Default) {
+ if (style.radius == 0) {
+ QPainterPath path(start);
+ path.lineTo(mid1);
+ path.lineTo(mid2);
+ path.lineTo(end);
+ return path;
+ } else {
+ const QLineF breakLine(mid1, mid2);
+ QPainterPath path1 = roundedCorner(start, mid1, breakLine.center(), style.radius);
+ QPainterPath path2 = roundedCorner(breakLine.center(), mid2, end, style.radius);
+ return path1 + path2;
+ }
+ } else {
+ return cubicBezier(start, mid1, mid2, end, style.bezier);
+ }
}
static void paintConnection(QPainter *painter,
@@ -911,11 +1065,6 @@ static void paintConnection(QPainter *painter,
horizontalFirst = false;
*/
- const qreal middleFactor = style.breakOffset / 100.0;
-
- QPointF startP;
- QLineF lastSegment;
-
bool extraLine = false;
if (horizontalFirst) {
@@ -937,95 +1086,47 @@ static void paintConnection(QPainter *painter,
}
}
+ QPointF startP;
+ QPointF endP;
+ QPainterPath path;
+
if (horizontalFirst) {
const qreal startX = boolExitRight ? from.right() + padding : from.x() - padding;
const qreal startY = from.center().y() + style.outOffset;
-
startP = QPointF(startX, startY);
- const qreal endY = (from.bottom() > to.y()) ? to.bottom() + padding : to.top() - padding;
-
if (!extraLine) {
- const qreal endX = to.center().x() + style.inOffset;
- const QPointF midP(endX, startY);
- const QPointF endP(endX, endY);
-
- if (style.radius == 0) {
- painter->drawLine(startP, midP);
- painter->drawLine(midP, endP);
- } else {
- drawRoundedCorner(painter, startP, midP, endP, style.radius);
- }
-
- lastSegment = QLineF(midP, endP);
+ const qreal endY = (from.bottom() > to.y()) ? to.bottom() + padding : to.top() - padding;
+ endP = QPointF(to.center().x() + style.inOffset, endY);
+ path = lShapedConnection(startP, endP, Qt::Horizontal, style);
} else {
const qreal endX = (from.right() > to.x()) ? to.right() + padding : to.left() - padding;
- const qreal midX = startX * middleFactor + endX * (1 - middleFactor);
- const QPointF midP(midX, startY);
- const QPointF midP2(midX, to.center().y() + style.inOffset);
- const QPointF endP(endX, to.center().y() + style.inOffset);
-
- if (style.radius == 0) {
- painter->drawLine(startP, midP);
- painter->drawLine(midP, midP2);
- painter->drawLine(midP2, endP);
- } else {
- const QLineF breakLine(midP, midP2);
- drawRoundedCorner(painter, startP, midP, breakLine.center(), style.radius);
- drawRoundedCorner(painter, breakLine.center(), midP2, endP, style.radius);
- }
-
- lastSegment = QLineF(midP2, endP);
+ endP = QPointF(endX, to.center().y() + style.inOffset);
+ path = sShapedConnection(startP, endP, Qt::Horizontal, style);
}
-
} else {
const qreal startX = from.center().x() + style.outOffset;
const qreal startY = boolExitBottom ? from.bottom() + padding : from.top() - padding;
-
startP = QPointF(startX, startY);
- const qreal endX = (from.right() > to.x()) ? to.right() + padding : to.left() - padding;
-
if (!extraLine) {
- const qreal endY = to.center().y() + style.inOffset;
- const QPointF midP(startX, endY);
- const QPointF endP(endX, endY);
-
- if (style.radius == 0) {
- painter->drawLine(startP, midP);
- painter->drawLine(midP, endP);
- } else {
- drawRoundedCorner(painter, startP, midP, endP, style.radius);
- }
-
- lastSegment = QLineF(midP, endP);
+ const qreal endX = (from.right() > to.x()) ? to.right() + padding : to.left() - padding;
+ endP = QPointF(endX, to.center().y() + style.inOffset);
+ path = lShapedConnection(startP, endP, Qt::Vertical, style);
} else {
const qreal endY = (from.bottom() > to.y()) ? to.bottom() + padding : to.top() - padding;
-
- const qreal midY = startY * middleFactor + endY * (1 - middleFactor);
- const QPointF midP(startX, midY);
- const QPointF midP2(to.center().x() + style.inOffset, midY);
- const QPointF endP(to.center().x() + style.inOffset, endY);
-
- if (style.radius == 0) {
- painter->drawLine(startP, midP);
- painter->drawLine(midP, midP2);
- painter->drawLine(midP2, endP);
- } else {
- const QLineF breakLine(midP, midP2);
- drawRoundedCorner(painter, startP, midP, breakLine.center(), style.radius);
- drawRoundedCorner(painter, breakLine.center(), midP2, endP, style.radius);
- }
-
- lastSegment = QLineF(midP2, endP);
+ endP = QPointF(to.center().x() + style.inOffset, endY);
+ path = sShapedConnection(startP, endP, Qt::Vertical, style);
}
}
+ painter->drawPath(path);
+
pen.setWidthF(style.width);
pen.setStyle(Qt::SolidLine);
painter->setPen(pen);
- drawArrow(painter, lastSegment, arrowLength, arrowWidth);
+ drawArrow(painter, QLineF(path.pointAtPercent(0.9), endP), arrowLength, arrowWidth);
painter->setBrush(Qt::white);
painter->drawEllipse(startP, arrowLength / 3, arrowLength / 3);
@@ -1142,6 +1243,22 @@ void FormEditorTransitionItem::paint(QPainter *painter, const QStyleOptionGraphi
if (qmlItemNode().modelNode().hasAuxiliaryData("radius"))
style.radius = qmlItemNode().modelNode().auxiliaryData("radius").toInt();
+ style.bezier = 50;
+
+ if (qmlItemNode().rootModelNode().hasAuxiliaryData("transitionBezier"))
+ style.bezier = qmlItemNode().rootModelNode().auxiliaryData("transitionBezier").toInt();
+
+ if (qmlItemNode().modelNode().hasAuxiliaryData("bezier"))
+ style.bezier = qmlItemNode().modelNode().auxiliaryData("bezier").toInt();
+
+ style.type = ConnectionType::Default;
+
+ if (qmlItemNode().rootModelNode().hasAuxiliaryData("transitionType"))
+ style.type = static_cast<ConnectionType>(qmlItemNode().rootModelNode().auxiliaryData("transitionType").toInt());
+
+ if (qmlItemNode().modelNode().hasAuxiliaryData("type"))
+ style.type = static_cast<ConnectionType>(qmlItemNode().modelNode().auxiliaryData("type").toInt());
+
if (resolved.isStartLine)
fromRect.translate(0, style.outOffset);
diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h
index d0fcd99319..8569059b7a 100644
--- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h
+++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h
@@ -47,6 +47,12 @@ namespace Internal {
class MoveController;
}
+enum ConnectionType
+{
+ Default = 0,
+ Bezier
+};
+
class ConnectionStyle
{
public:
@@ -58,6 +64,8 @@ public:
int inOffset;
int breakOffset;
int radius;
+ int bezier;
+ ConnectionType type;
};
class QMLDESIGNERCORE_EXPORT FormEditorItem : public QGraphicsItem
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
index 1a6025acef..171046b4ae 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
@@ -167,10 +167,18 @@ QVariant properDefaultAuxiliaryProperties(const QmlObjectNode &qmlObjectNode,
return 0;
else if (propertyName == "breakPoint")
return 50;
+ else if (propertyName == "transitionType")
+ return 0;
+ else if (propertyName == "type")
+ return 0;
else if (propertyName == "transitionRadius")
return 8;
else if (propertyName == "radius")
return 8;
+ else if (propertyName == "transitionBezier")
+ return 50;
+ else if (propertyName == "bezier")
+ return 50;
else if (propertyName == "customId")
return QString();
else if (propertyName == "joinConnection")
@@ -240,7 +248,7 @@ void PropertyEditorQmlBackend::setupAuxiliaryProperties(const QmlObjectNode &qml
propertyNames.append("customId");
if (itemNode.isFlowTransition()) {
- propertyNames.append({"color", "width", "inOffset", "outOffset", "dash", "breakPoint", "radius"});
+ propertyNames.append({"color", "width", "inOffset", "outOffset", "dash", "breakPoint", "type", "radius", "bezier"});
} else if (itemNode.isFlowItem()) {
propertyNames.append({"color", "width", "inOffset", "outOffset", "joinConnection"});
} else if (itemNode.isFlowActionArea()) {
@@ -250,7 +258,7 @@ void PropertyEditorQmlBackend::setupAuxiliaryProperties(const QmlObjectNode &qml
} else if (itemNode.isFlowWildcard()) {
propertyNames.append({"color", "width", "fillColor", "dash"});
} else if (itemNode.isFlowView()) {
- propertyNames.append({"transitionColor", "areaColor", "areaFillColor", "blockColor", "transitionRadius"});
+ propertyNames.append({"transitionColor", "areaColor", "areaFillColor", "blockColor", "transitionType", "transitionRadius", "transitionBezier"});
}
for (const PropertyName &propertyName : propertyNames) {