Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickregularpolygonshape.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7#include <algorithm>
8#include <QtMath>
9#include <cstddef>
10
11QT_BEGIN_NAMESPACE
12
13QQuickRegularPolygonShapePrivate::QQuickRegularPolygonShapePrivate() = default;
14
15QQuickRegularPolygonShapePrivate::~QQuickRegularPolygonShapePrivate() = default;
16
17namespace {
18
19qreal intersect(QVector2D p, QVector2D dir1, QVector2D q, QVector2D dir2)
20{
21 const auto r = dir1.normalized();
22 const auto s = dir2.normalized();
23
24 const QVector2D pq(q.x() - p.x(), q.y() - p.y());
25 const QVector2D snv(s.y(), -s.x());
26
28}
29
30} // namespace
31
32void QQuickRegularPolygonShapePrivate::updatePoints()
33{
34 points.clear();
35
36 const qreal sliceAngle = 360.0f / sideCount;
37 const qreal a = width.valueBypassingBindings() / 2.0f; // x-radius
38 const qreal b = height.valueBypassingBindings() / 2.0f; // y-radius
39 const QVector2D center(a, b);
40
41 for (int i = 0; i < sideCount; ++i) {
42 const qreal angleToCorner = qDegreesToRadians(i * sliceAngle);
43
44 // Start at top center
45 const QVector2D xy = center + QVector2D(a * qSin(angleToCorner), -b * qCos(angleToCorner));
46
47 points.push_back(std::move(xy));
48 }
49}
50
51void QQuickRegularPolygonShapePrivate::updateBisectors()
52{
53 const auto size = points.size();
54
55 bisectors.clear();
56
57 // A list of vectors that are the bisectors of the inner angles of the polygon.
58 // This is used to calculate the intersection point of neighboring bisectors for a corner.
59 // The minimum length of the two neighboring corner bisectors intersection point is the
60 // maximum for the center of the circle that make up the corner radius.
61 for (size_t i = 0; i < size; ++i) {
62 const auto &a = points[i];
63 const auto &b = points[(i == 0 ? size : i) - 1];
64 const auto &c = points[(i + 1 == size) ? 0 : (i + 1)];
65
66 const QVector2D vAB(b.x() - a.x(), b.y() - a.y());
67 const QVector2D vAC(c.x() - a.x(), c.y() - a.y());
68 const auto bisector = (vAB + vAC).normalized();
69
70 bisectors.push_back(std::move(bisector));
71 }
72}
73
74void QQuickRegularPolygonShapePrivate::constructPolygonPath()
75{
76 auto *ppath = QQuickShapePathPrivate::get(path);
77
78 // start path
79 path->setStartX(points[0].x());
80 path->setStartY(points[0].y());
81
82 for (const auto &p : points) {
83 auto line = new QQuickPathLine(path);
84 line->setX(p.x());
85 line->setY(p.y());
86 ppath->appendPathElement(line, QQuickPathPrivate::ProcessPathPolicy::DontProcess);
87 }
88
89 // close path
90 auto line = new QQuickPathLine(path);
91 line->setX(points[0].x());
92 line->setY(points[0].y());
93 ppath->appendPathElement(line, QQuickPathPrivate::ProcessPathPolicy::DontProcess);
94
95 path->processPath();
96}
97
98void QQuickRegularPolygonShapePrivate::constructRoundedPolygonPath()
99{
100 auto *ppath = QQuickShapePathPrivate::get(path);
101
102 updateBisectors();
103
104 const auto size = points.size();
105 for (size_t i = 0; i < size; ++i) {
106 const size_t idxMinusOne = (i == 0 ? size : i) - 1;
107 const size_t idxPlusOne = (i + 1 == size) ? 0 : (i + 1);
108 const auto &a = points[i];
109 const auto &b = points[idxMinusOne];
110 const auto &c = points[idxPlusOne];
111 qreal r = cornerRadius;
112
113 const QVector2D vAB(b.x() - a.x(), b.y() - a.y());
114 const QVector2D vAC(c.x() - a.x(), c.y() - a.y());
115
116 // Calculate the intersection points of the two neighboring bisectors
117 const qreal tAB =
118 intersect(a, bisectors[i],
119 b, bisectors[idxMinusOne]);
120 const qreal tAC =
121 intersect(a, bisectors[i],
122 c, bisectors[idxPlusOne]);
123 const qreal tMax = std::min(tAB, tAC);
124
125 // Angle between the two vectors AB and AC as radians
126 const qreal alpha = qAcos(QVector2D::dotProduct(vAB, vAC) / (vAB.length() * vAC.length()));
127
128 // The maximum radius of the circle that can be drawn at the corner. This is another
129 // constraint that Figma uses to calculate the corner radius. The corner radius shouldn't
130 // be bigger than half of the distance between the two neighboring corners.
131 const qreal maxRadius = std::round(QVector2D(c.x() - b.x(), c.y() - b.y()).length() / 2);
132 r = std::min(r, maxRadius);
133
134 // The optimal length of the corner bisector to place the center of the circle.
135 const qreal cLength = r / (qSin(alpha / 2));
136
137 // Clamp c to the maximum value found from the intersection points of the bisectors.
138 const qreal realC = std::min(cLength, tMax);
139
140 if (realC < cLength)
141 r = realC * qSin(alpha / 2);
142
143 const qreal t = qSqrt(qPow(realC, 2) - qPow(r, 2));
144
145 const auto p1 = (vAB.normalized() * t) + QVector2D(a.x(), a.y());
146 const auto p2 = (vAC.normalized() * t) + QVector2D(a.x(), a.y());
147
148 if (i == 0) {
149 path->setStartX(p1.x());
150 path->setStartY(p1.y());
151 } else {
152 auto line = new QQuickPathLine(path);
153 line->setX(p1.x());
154 line->setY(p1.y());
155 ppath->appendPathElement(line, QQuickPathPrivate::ProcessPathPolicy::DontProcess);
156 }
157
158 auto arc = new QQuickPathArc(path);
159 arc->setX(p2.x());
160 arc->setY(p2.y());
161 arc->setRadiusX(r);
162 arc->setRadiusY(r);
163 ppath->appendPathElement(arc, QQuickPathPrivate::ProcessPathPolicy::DontProcess);
164 }
165
166 // Close the polygon
167 auto line = new QQuickPathLine(path);
168 line->setX(path->startX());
169 line->setY(path->startY());
170 ppath->appendPathElement(line, QQuickPathPrivate::ProcessPathPolicy::DontProcess);
171
172 path->processPath();
173}
174
175void QQuickRegularPolygonShapePrivate::updatePath()
176{
177 QQuickShapePathPrivate::get(path)->clearPathElements(
178 QQuickPathPrivate::DeleteElementPolicy::Delete);
179
180 updatePoints();
181
182 if (qFuzzyCompare(cornerRadius, 0.0))
183 constructPolygonPath();
184 else
185 constructRoundedPolygonPath();
186}
187
188/*!
189 \qmltype RegularPolygonShape
190 \inqmlmodule QtQuick.Shapes.DesignHelpers
191 \brief A filled regular polygon with an optional border.
192 \since QtQuick 6.10
193
194 A regular polygon can be just a 2D polygon shaped stroke, a filling, or a
195 stroke with filling. The \l strokeColor, \l strokeWidth, and \l strokeStyle
196 properties specify the appearance of the outline. The \l dashPattern and
197 \l dashOffset properties specify the appearance of dashed stroke.
198
199 The area inside the stroke is painted using either a solid fill color,
200 specified using the \l fillColor property, or a gradient, defined using
201 one of the \l ShapeGradient subtypes and set using the \l gradient
202 property. If both a color and a gradient are specified, the gradient is
203 used.
204
205 To create a polygon with a stroke, set the \sideCount property between 3 to
206 100 and the \l strokeWidth property greater than 0. The \l strokeWidth
207 property specifies the width of the polygon stroke. The default \l sideCount
208 value is 6 and the default \l strokeWidth value is 4. Setting the
209 \l strokeWidth value to a negetive value hides the border.
210
211 The \l cornerRadius property specifies whether the polygon corners are rounded.
212*/
213
214QQuickRegularPolygonShape::QQuickRegularPolygonShape(QQuickItem *parent)
215 : QQuickShape(*(new QQuickRegularPolygonShapePrivate), parent)
216{
217 Q_D(QQuickRegularPolygonShape);
218
219 setPreferredRendererType(CurveRenderer);
220
221 setWidth(200);
222 setHeight(200);
223
224 d->path = new QQuickShapePath(this);
225 d->path->setStrokeWidth(1);
226 d->path->setStrokeColor(QColorConstants::Black);
227 d->path->setFillColor(QColorConstants::White);
228
229 d->sp.append(d->path);
230 d->path->setParent(this);
231 d->extra.value().resourcesList.append(d->path);
232
233 connect(d->path, &QQuickShapePath::strokeColorChanged, this, &QQuickRegularPolygonShape::strokeColorChanged);
234 connect(d->path, &QQuickShapePath::strokeWidthChanged, this, &QQuickRegularPolygonShape::strokeWidthChanged);
235 connect(d->path, &QQuickShapePath::fillColorChanged, this, &QQuickRegularPolygonShape::fillColorChanged);
236 connect(d->path, &QQuickShapePath::joinStyleChanged, this, &QQuickRegularPolygonShape::joinStyleChanged);
237 connect(d->path, &QQuickShapePath::capStyleChanged, this, &QQuickRegularPolygonShape::capStyleChanged);
238 connect(d->path, &QQuickShapePath::strokeStyleChanged, this, &QQuickRegularPolygonShape::strokeStyleChanged);
239 connect(d->path, &QQuickShapePath::dashOffsetChanged, this, &QQuickRegularPolygonShape::dashOffsetChanged);
240 connect(d->path, &QQuickShapePath::dashPatternChanged, this, &QQuickRegularPolygonShape::dashPatternChanged);
241 connect(d->path, &QQuickShapePath::fillItemChanged, this, &QQuickRegularPolygonShape::fillItemChanged);
242 connect(d->path, &QQuickShapePath::fillGradientChanged, this, &QQuickRegularPolygonShape::fillGradientChanged);
243}
244
245QQuickRegularPolygonShape::~QQuickRegularPolygonShape() = default;
246
247/*!
248 \include shapepath.qdocinc {dashOffset-property}
249 {QtQuick.Shapes.DesignHelpers::RegularPolygonShape}
250*/
251
252qreal QQuickRegularPolygonShape::dashOffset() const
253{
254 Q_D(const QQuickRegularPolygonShape);
255 return d->path->dashOffset();
256}
257
258void QQuickRegularPolygonShape::setDashOffset(qreal offset)
259{
260 Q_D(QQuickRegularPolygonShape);
261 d->path->setDashOffset(offset);
262}
263
264/*!
265 \qmlproperty real QtQuick.Shapes.DesignHelpers::RegularPolygonShape::cornerRadius
266
267 The property property specifies whether the polygon corners are rounded.
268
269 The default value is \c 10.
270*/
271
272qreal QQuickRegularPolygonShape::cornerRadius() const
273{
274 Q_D(const QQuickRegularPolygonShape);
275 return d->cornerRadius;
276}
277
278void QQuickRegularPolygonShape::setCornerRadius(qreal radius)
279{
280 Q_D(QQuickRegularPolygonShape);
281 if (qFuzzyCompare(d->cornerRadius, radius))
282 return;
283 d->cornerRadius = radius;
284 d->updatePath();
285 emit cornerRadiusChanged();
286}
287
288/*!
289 \qmlproperty int QtQuick.Shapes.DesignHelpers::RegularPolygonShape::sideCount
290
291 The number of edges on the regular polygon. The minimum number of edges can
292 be 3.
293
294 The default value is \c 6.
295*/
296
297int QQuickRegularPolygonShape::sideCount() const
298{
299 Q_D(const QQuickRegularPolygonShape);
300 return d->sideCount;
301}
302
303void QQuickRegularPolygonShape::setSideCount(int sideCount)
304{
305 Q_D(QQuickRegularPolygonShape);
306 if (d->sideCount == sideCount)
307 return;
308 d->sideCount = sideCount;
309 d->updatePath();
310 emit sideCountChanged();
311}
312
313/*!
314 \qmlproperty real QtQuick.Shapes.DesignHelpers::RegularPolygonShape::strokeWidth
315
316 This property holds the stroke width.
317
318 When set to a negative value, no stroking occurs.
319
320 The default value is \c 1.
321*/
322
323qreal QQuickRegularPolygonShape::strokeWidth() const
324{
325 Q_D(const QQuickRegularPolygonShape);
326 return d->path->strokeWidth();
327}
328
329void QQuickRegularPolygonShape::setStrokeWidth(qreal width)
330{
331 Q_D(QQuickRegularPolygonShape);
332 d->path->setStrokeWidth(width);
333}
334
335/*!
336 \qmlproperty color QtQuick.Shapes.DesignHelpers::RegularPolygonShape::fillColor
337
338 This property holds the fill color.
339
340 When set to \c transparent, no filling occurs.
341
342 The default value is \c "white".
343
344 \note If either \l fillGradient is set to something other than \c null, it
345 will be used instead of \c fillColor.
346*/
347
348QColor QQuickRegularPolygonShape::fillColor() const
349{
350 Q_D(const QQuickRegularPolygonShape);
351 return d->path->fillColor();
352}
353
354void QQuickRegularPolygonShape::setFillColor(const QColor &color)
355{
356 Q_D(QQuickRegularPolygonShape);
357 d->path->setFillColor(color);
358}
359
360/*!
361 \qmlproperty color QtQuick.Shapes.DesignHelpers::RegularPolygonShape::strokeColor
362
363 This property holds the stroking color.
364
365 When set to \c transparent, no stroking occurs.
366
367 The default value is \c "black".
368*/
369
370QColor QQuickRegularPolygonShape::strokeColor() const
371{
372 Q_D(const QQuickRegularPolygonShape);
373 return d->path->strokeColor();
374}
375
376void QQuickRegularPolygonShape::setStrokeColor(const QColor &color)
377{
378 Q_D(QQuickRegularPolygonShape);
379 d->path->setStrokeColor(color);
380}
381
382/*!
383 \include shapepath.qdocinc {capStyle-property}
384 {QtQuick.Shapes.DesignHelpers::RegularPolygonShape}
385
386 Since a polygon is drawn, the path forms a loop with no line end points.
387 Therefore, capStyle is only needed when strokeStyle == ShapePath.DashLine
388*/
389
390QQuickShapePath::CapStyle QQuickRegularPolygonShape::capStyle() const
391{
392 Q_D(const QQuickRegularPolygonShape);
393 return d->path->capStyle();
394}
395
396void QQuickRegularPolygonShape::setCapStyle(QQuickShapePath::CapStyle style)
397{
398 Q_D(QQuickRegularPolygonShape);
399 d->path->setCapStyle(style);
400}
401
402/*!
403 \include shapepath.qdocinc {joinStyle-property}
404 {QtQuick.Shapes.DesignHelpers::RegularPolygonShape}
405
406 The joinStyle is only meaningful if cornerRadius == 0.
407*/
408
409QQuickShapePath::JoinStyle QQuickRegularPolygonShape::joinStyle() const
410{
411 Q_D(const QQuickRegularPolygonShape);
412 return d->path->joinStyle();
413}
414
415void QQuickRegularPolygonShape::setJoinStyle(QQuickShapePath::JoinStyle style)
416{
417 Q_D(QQuickRegularPolygonShape);
418 d->path->setJoinStyle(style);
419}
420
421/*!
422 \include shapepath.qdocinc {strokeStyle-property}
423 {QtQuick.Shapes.DesignHelpers::RegularPolygonShape}
424*/
425
426QQuickShapePath::StrokeStyle QQuickRegularPolygonShape::strokeStyle() const
427{
428 Q_D(const QQuickRegularPolygonShape);
429 return d->path->strokeStyle();
430}
431
432void QQuickRegularPolygonShape::setStrokeStyle(QQuickShapePath::StrokeStyle style)
433{
434 Q_D(QQuickRegularPolygonShape);
435 d->path->setStrokeStyle(style);
436}
437
438/*!
439 \include shapepath.qdocinc {dashPattern-property}
440 {QtQuick.Shapes.DesignHelpers::RegularPolygonShape}
441*/
442
443QList<qreal> QQuickRegularPolygonShape::dashPattern() const
444{
445 Q_D(const QQuickRegularPolygonShape);
446 return d->path->dashPattern();
447}
448
449void QQuickRegularPolygonShape::setDashPattern(const QList<qreal> &array)
450{
451 Q_D(QQuickRegularPolygonShape);
452 d->path->setDashPattern(array);
453}
454
455/*!
456 \qmlproperty ShapeGradient QtQuick.Shapes.DesignHelpers::RegularPolygonShape::fillGradient
457
458 The fillGradient of the polygon fill color.
459
460 By default, no fillGradient is enabled and the value is null. In this case, the
461 fill uses a solid color based on the value of \l fillColor.
462
463 When set, \l fillColor is ignored and filling is done using one of the
464 \l ShapeGradient subtypes.
465
466 \note The \l Gradient type cannot be used here. Rather, prefer using one of
467 the advanced subtypes, like \l LinearGradient.
468*/
469QQuickShapeGradient *QQuickRegularPolygonShape::fillGradient() const
470{
471 Q_D(const QQuickRegularPolygonShape);
472 return d->path->fillGradient();
473}
474
475void QQuickRegularPolygonShape::setFillGradient(QQuickShapeGradient *fillGradient)
476{
477 Q_D(QQuickRegularPolygonShape);
478 d->path->setFillGradient(fillGradient);
479}
480
481void QQuickRegularPolygonShape::resetFillGradient()
482{
483 setFillGradient(nullptr);
484}
485
486QQuickItem *QQuickRegularPolygonShape::fillItem() const
487{
488 Q_D(const QQuickRegularPolygonShape);
489 return d->path->fillItem();
490}
491
492void QQuickRegularPolygonShape::setFillItem(QQuickItem *newFillItem)
493{
494 Q_D(QQuickRegularPolygonShape);
495 d->path->setFillItem(newFillItem);
496}
497
498void QQuickRegularPolygonShape::itemChange(ItemChange change, const ItemChangeData &value)
499{
500 Q_D(QQuickRegularPolygonShape);
501
502 if (d->path)
503 d->updatePath();
504
505 QQuickItem::itemChange(change, value);
506}
507
508QT_END_NAMESPACE
509
510#include "moc_qquickregularpolygonshape_p.cpp"
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:32
constexpr float y() const noexcept
Returns the y coordinate of this point.
Definition qvectornd.h:503
QVector2D normalized() const noexcept
Returns the normalized unit vector form of this vector.
Definition qvectornd.h:530
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:502
static constexpr float dotProduct(QVector2D v1, QVector2D v2) noexcept
Returns the dot product of v1 and v2.
Definition qvectornd.h:605