aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenning Gruendl <henning.gruendl@qt.io>2024-08-09 16:58:24 +0200
committerHenning Gruendl <henning.gruendl@qt.io>2024-08-26 13:31:42 +0200
commite8597ac3756c350cd9ad6905f385bfef3ad24753 (patch)
tree6398ddcfb4599add0a3caedae408795381e7d471
parent80b9e31a8863c9bf2a57c3d569b761d9df71cdb3 (diff)
QmlDesigner: Update RegularPolygonItem
Update the construction of RegularPolygonItem so it will be same as in Figma. Task-number: QDS-13304 Change-Id: I9f2d323eb3e01e2f39be7290fe6e6736d9e57e6d Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
-rw-r--r--src/imports/components/RegularPolygonItem.qml241
-rw-r--r--src/imports/components/designer/RegularPolygonItemSpecifics.qml14
-rw-r--r--src/imports/components/qmldir15
3 files changed, 162 insertions, 108 deletions
diff --git a/src/imports/components/RegularPolygonItem.qml b/src/imports/components/RegularPolygonItem.qml
index 8a0e4d2..4f44771 100644
--- a/src/imports/components/RegularPolygonItem.qml
+++ b/src/imports/components/RegularPolygonItem.qml
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2024 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick Studio Components.
@@ -50,9 +50,7 @@ Shape {
The default value is 10.
If radius is non-zero, the corners will be rounded, otherwise they will
- be sharp. The radius can also be specified separately for each corner by
- using the \l bottomLeftRadius, \l bottomRightRadius, \l topLeftRadius, and
- \l topRightRadius properties.
+ be sharp.
*/
property int radius: 10
@@ -159,11 +157,11 @@ Shape {
property alias dashOffset: path.dashOffset
/*!
- Number of sides on the polygon.
+ The number of sides on the polygon.
*/
property int sideCount: 6
- property bool __preferredRendererTypeAvailable: root.preferredRendererType !== undefined
+ property bool __preferredRendererTypeAvailable: root.preferredRendererType !== "undefined"
property bool __curveRendererActive: root.__preferredRendererTypeAvailable
&& root.rendererType === Shape.CurveRenderer
@@ -191,14 +189,10 @@ Shape {
startY: 0
}
+ onWidthChanged: root.constructPolygon()
+ onHeightChanged: root.constructPolygon()
onSideCountChanged: root.constructPolygon()
- onRadiusChanged: {
- // Only construct polygon if radius changed from 0 to 1 or vice versa.
- if ((root.radius + root.__previousRadius) === 1)
- root.constructPolygon()
-
- root.__previousRadius = root.radius
- }
+ onRadiusChanged: root.constructPolygon()
Component.onCompleted: {
// If preferredRendererType wasn't set initially make CurveRenderer the default
@@ -210,20 +204,12 @@ Shape {
property real __centerX: root.width / 2
property real __centerY: root.height / 2
- property real __radius: Math.min(root.width, root.height) / 2
-
- property int __previousRadius: root.radius
-
- property int minRadius: 0
- property int maxRadius: root.__radius * Math.cos(root.toRadians(180.0 / root.sideCount))
-
- property int __actualRadius: Math.max(root.minRadius, Math.min(root.maxRadius, root.radius))
function constructPolygon() {
root.clearPathElements()
if (root.radius === 0)
- root.constructNonRoundedPolygonPath()
+ root.constructPolygonPath()
else
root.constructRoundedPolygonPath()
}
@@ -232,96 +218,165 @@ Shape {
return degrees * (Math.PI / 180.0)
}
- function constructNonRoundedPolygonPath() {
- for (var cornerNumber = 0; cornerNumber < root.sideCount; cornerNumber++) {
- let angleToCorner = root.toRadians(cornerNumber * (360.0 / root.sideCount))
-
- if (cornerNumber === 0) {
- path.startX = Qt.binding(function() {
- return root.__centerX + root.__radius * Math.cos(0)
- })
- path.startY = Qt.binding(function() {
- return root.__centerY + root.__radius * Math.sin(0)
- })
+ function toDegrees(radians) {
+ return radians * (180.0 / Math.PI)
+ }
+
+ function getPoints() {
+ let sliceAngle = (360.0 / root.sideCount)
+ // The Draftsman's Method
+ let a = root.width / 2 // x-radius
+ let b = root.height / 2 // y-radius
+ let points = []
+
+ // Go clockwise from top center
+ for (var corner = 0; corner < root.sideCount; corner++) {
+ let angleToCorner = root.toRadians(corner * sliceAngle)
+ // Start at top center
+ let x = root.__centerX + a * Math.sin(angleToCorner)
+ let y = root.__centerY - b * Math.cos(angleToCorner)
+
+ points.push(Qt.point(x ,y))
+ }
+
+ return points
+ }
+
+ Component {
+ id: myPathLine
+ PathLine {}
+ }
+
+ Component {
+ id: myPathArc
+ PathArc {}
+ }
+
+ function constructPolygonPath() {
+ let angle = (360.0 / root.sideCount)
+ let points = root.getPoints()
+
+ for (var i = 0; i < points.length; i++) {
+ if (i === 0) {
+ path.startX = points[i].x
+ path.startY = points[i].y
} else {
- let pathLine = Qt.createQmlObject('import QtQuick 2.15; PathLine {}', path)
- pathLine.x = Qt.binding(function() {
- return root.__centerX + root.__radius * Math.cos(angleToCorner)
- })
- pathLine.y = Qt.binding(function() {
- return root.__centerY + root.__radius * Math.sin(angleToCorner)
- })
+ let pathLine = myPathLine.createObject(path)
+ pathLine.x = points[i].x
+ pathLine.y = points[i].y
path.pathElements.push(pathLine)
}
}
// Close the polygon
- var pathLineClose = Qt.createQmlObject('import QtQuick 2.15; PathLine {}', path)
- pathLineClose.x = Qt.binding(function() { return path.startX } )
- pathLineClose.y = Qt.binding(function() { return path.startY } )
+ var pathLineClose = myPathLine.createObject(path)
+ pathLineClose.x = points[0].x
+ pathLineClose.y = points[0].y
path.pathElements.push(pathLineClose)
}
- property real __halfInteriorCornerAngle: 90 - (180.0 / root.sideCount)
- property real __halfCornerArcSweepAngle: 90 - root.__halfInteriorCornerAngle
- property real __distanceToCornerArcCenter: root.__radius - root.__actualRadius /
- Math.sin(root.toRadians(root.__halfInteriorCornerAngle))
+ // https://stackoverflow.com/questions/58541430/find-intersection-point-of-two-vectors-independent-from-direction
+ // This function returns the length of the vector from p to the intersection point of the two lines
+ // both defined by a point and a vector.
+ function intersect(p: point, dir1: vector2d, q: point, dir2: vector2d) : real {
+ let r = dir1.normalized()
+ let s = dir2.normalized()
+
+ let pq = Qt.vector2d(q.x - p.x, q.y - p.y)
+ let snv = Qt.vector2d(s.y, -s.x);
+
+ return pq.dotProduct(snv) / r.dotProduct(snv)
+ }
+
+ function wrapIndex(index, size) {
+ return (index + size) % size
+ }
function constructRoundedPolygonPath() {
- for (var cornerNumber = 0; cornerNumber < root.sideCount; cornerNumber++) {
- let angleToCorner = cornerNumber * (360.0 / root.sideCount)
-
- let pathArc = Qt.createQmlObject('import QtQuick 2.15; PathArc {
- property real centerX;
- property real centerY }', path)
- pathArc.centerX = Qt.binding(function() {
- return root.__centerX + root.__distanceToCornerArcCenter
- * Math.cos(root.toRadians(angleToCorner))
- })
- pathArc.centerY = Qt.binding(function() {
- return root.__centerY + root.__distanceToCornerArcCenter
- * Math.sin(root.toRadians(angleToCorner))
- })
- pathArc.x = Qt.binding(function() {
- return pathArc.centerX + root.__actualRadius
- * (Math.cos(root.toRadians(angleToCorner + root.__halfCornerArcSweepAngle)))
- })
- pathArc.y = Qt.binding(function() {
- return pathArc.centerY + root.__actualRadius
- * (Math.sin(root.toRadians(angleToCorner + root.__halfCornerArcSweepAngle)))
- })
- pathArc.radiusX = Qt.binding(function() { return root.__actualRadius })
- pathArc.radiusY = Qt.binding(function() { return root.__actualRadius })
-
- if (cornerNumber === 0) {
- path.startX = Qt.binding(function() {
- return pathArc.centerX + root.__actualRadius
- * (Math.cos(root.toRadians(angleToCorner - root.__halfCornerArcSweepAngle)))
- })
- path.startY = Qt.binding(function() {
- return pathArc.centerY + root.__actualRadius
- * (Math.sin(root.toRadians(angleToCorner - root.__halfCornerArcSweepAngle)))
- })
+ let angle = (360.0 / root.sideCount)
+ let points = root.getPoints()
+
+ // A list of vectors that are the bisectors of the inner angles of the polygon.
+ // This is used to calculate the intersection point of neighboring bisectors for a corner.
+ // The minimum length of the two neighboring corner bisectors intersection point is the
+ // maximum for the center of the circle that make up the corner radius.
+ let bisectors = []
+
+ // Create angle bisectors by using the parallelolgram rule.
+ for (var i = 0; i < points.length; i++) {
+ let a = points[root.wrapIndex(i, points.length)]
+ let b = points[root.wrapIndex(i - 1, points.length)]
+ let c = points[root.wrapIndex(i + 1, points.length)]
+
+ let vAB = Qt.vector2d(b.x - a.x, b.y - a.y).normalized()
+ let vAC = Qt.vector2d(c.x - a.x, c.y - a.y).normalized()
+ let bisector = vAB.plus(vAC).normalized()
+
+ bisectors.push(bisector)
+ }
+
+ for (var i = 0; i < points.length; i++) {
+ let a = points[root.wrapIndex(i, points.length)]
+ let b = points[root.wrapIndex(i - 1, points.length)]
+ let c = points[root.wrapIndex(i + 1, points.length)]
+ let r = root.radius
+
+ let vAB = Qt.vector2d(b.x - a.x, b.y - a.y)
+ let vAC = Qt.vector2d(c.x - a.x, c.y - a.y)
+
+ // Calculate the intersection points of the two neighboring bisectors
+ let tAB = root.intersect(a, bisectors[root.wrapIndex(i, bisectors.length)],
+ b, bisectors[root.wrapIndex(i - 1, bisectors.length)])
+ let tAC = root.intersect(a, bisectors[root.wrapIndex(i, bisectors.length)],
+ c, bisectors[root.wrapIndex(i + 1, bisectors.length)])
+ let tMax = Math.min(tAB, tAC)
+
+ // Angle between the two vectors AB and AC as radians
+ let alpha = Math.acos(vAB.dotProduct(vAC) / (vAB.length() * vAC.length()))
+
+ // The maximum radius of the circle that can be drawn at the corner. This is another
+ // constraint that Figma uses to calculate the corner radius. The corner radius shouldn't
+ // be bigger than half of the distance between the two neighboring corners.
+ let maxRadius = Math.round(Qt.vector2d(c.x - b.x, c.y - b.y).length() / 2)
+ r = Math.min(r, maxRadius)
+
+ // The optimal length of the corner bisector to place the center of the circle.
+ let cLength = r / (Math.sin(alpha / 2))
+
+ // Clamp c to the maximum value found from the intersection points of the bisectors.
+ let realC = Math.min(cLength, tMax)
+
+ if (realC < cLength)
+ r = realC * Math.sin(alpha / 2)
+
+ let t = Math.sqrt(Math.pow(realC, 2) - Math.pow(r, 2))
+
+ let p1 = vAB.normalized().times(t).plus(Qt.vector2d(a.x, a.y))
+ let p2 = vAC.normalized().times(t).plus(Qt.vector2d(a.x, a.y))
+
+ if (i === 0) {
+ path.startX = p1.x
+ path.startY = p1.y
} else {
- let pathLine = Qt.createQmlObject('import QtQuick 2.15; PathLine {}', path)
- pathLine.x = Qt.binding(function() {
- return pathArc.centerX + root.__actualRadius
- * (Math.cos(root.toRadians(angleToCorner - root.__halfCornerArcSweepAngle)))
- })
- pathLine.y = Qt.binding(function() {
- return pathArc.centerY + root.__actualRadius
- * (Math.sin(root.toRadians(angleToCorner - root.__halfCornerArcSweepAngle)))
- })
+ let pathLine = myPathLine.createObject(path)
+ pathLine.x = p1.x
+ pathLine.y = p1.y
path.pathElements.push(pathLine)
}
+ let pathArc = myPathArc.createObject(path)
+ pathArc.x = p2.x
+ pathArc.y = p2.y
+ pathArc.radiusX = r
+ pathArc.radiusY = r
+
path.pathElements.push(pathArc)
}
// Close the polygon
- var pathLineClose = Qt.createQmlObject('import QtQuick 2.15; PathLine {}', path)
- pathLineClose.x = Qt.binding(function() { return path.startX} )
- pathLineClose.y = Qt.binding(function() { return path.startY} )
+ var pathLineClose = myPathLine.createObject(path)
+ pathLineClose.x = path.startX
+ pathLineClose.y = path.startY
path.pathElements.push(pathLineClose)
}
diff --git a/src/imports/components/designer/RegularPolygonItemSpecifics.qml b/src/imports/components/designer/RegularPolygonItemSpecifics.qml
index 46bc42b..0b4ec81 100644
--- a/src/imports/components/designer/RegularPolygonItemSpecifics.qml
+++ b/src/imports/components/designer/RegularPolygonItemSpecifics.qml
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2024 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick Designer Components.
@@ -27,10 +27,10 @@
**
****************************************************************************/
-import QtQuick 2.15
-import QtQuick.Layouts 1.15
-import HelperWidgets 2.0
-import StudioTheme 1.0 as StudioTheme
+import QtQuick
+import QtQuick.Layouts
+import HelperWidgets
+import StudioTheme as StudioTheme
Column {
anchors.left: parent.left
@@ -153,8 +153,8 @@ Column {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
decimals: 0
- minimumValue: backendValues.minRadius.value
- maximumValue: backendValues.maxRadius.value
+ minimumValue: 0
+ maximumValue: 1000
stepSize: 1
}
diff --git a/src/imports/components/qmldir b/src/imports/components/qmldir
index 4fdc28d..ceb4bc9 100644
--- a/src/imports/components/qmldir
+++ b/src/imports/components/qmldir
@@ -1,14 +1,13 @@
+ArcArrow 1.0 ArcArrow.qml
ArcItem 1.0 ArcItem.qml
-PieItem 1.0 PieItem.qml
-TriangleItem 1.0 TriangleItem.qml
-SvgPathItem 1.0 SvgPathItem.qml
+BorderItem 1.0 BorderItem.qml
EllipseItem 1.0 EllipseItem.qml
FlipableItem 1.0 FlipableItem.qml
+GroupItem 1.0 GroupItem.qml
+IsoItem 1.0 IsoItem.qml
+PieItem 1.0 PieItem.qml
RectangleItem 1.0 RectangleItem.qml
RegularPolygonItem 1.0 RegularPolygonItem.qml
-BorderItem 1.0 BorderItem.qml
-IsoItem 1.0 IsoItem.qml
-GroupItem 1.0 GroupItem.qml
-ArcArrow 1.0 ArcArrow.qml
StraightArrow 1.0 StraightArrow.qml
-
+SvgPathItem 1.0 SvgPathItem.qml
+TriangleItem 1.0 TriangleItem.qml