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