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
qquickellipseshape.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
10QT_BEGIN_NAMESPACE
11
12namespace {
13inline bool is_equal(qreal a, qreal b, qreal epsilon = DBL_EPSILON)
14{
15 return std::fabs(a - b) <= epsilon;
16}
17
18inline qreal arc_angle(qreal angle)
19{
20 return angle - 90;
21}
22
23inline QVector2D arc_point(QVector2D center, QVector2D radius, qreal angle)
24{
25 return QVector2D(center.x() + radius.x() * qCos(qDegreesToRadians(angle)),
26 center.y() + radius.y() * qSin(qDegreesToRadians(angle)));
27}
28
29// counter-clockwise
30inline QVector2D tangent_ccw(QVector2D radius, qreal angle)
31{
32 return QVector2D(-radius.x() * qSin(qDegreesToRadians(angle)),
33 radius.y() * qCos(qDegreesToRadians(angle)));
34}
35
36inline qreal cross(QVector2D a, QVector2D b)
37{
38 return a.x() * b.y() - a.y() * b.x();
39}
40
41// cross product of two vectors defined by points A->B x C->D
42inline qreal cross(QVector2D a, QVector2D b, QVector2D c, QVector2D d)
43{
44 return cross(b - a, d - c);
45}
46
47qreal angle_between_vectors(QVector2D a, QVector2D b)
48{
49 const QVector2D uA = a.normalized();
50 const QVector2D uB = b.normalized();
51 const qreal angle = qAtan2(cross(uA, uB), QVector2D::dotProduct(uA, uB));
52 if (std::fabs(angle) < FLT_EPSILON)
53 return 0.0f;
54 return angle;
55}
56
57// the intersection point can be calculated as C + T * (D - C) or A + S * (B - A)
58bool lines_intersect(QVector2D a, QVector2D b, QVector2D c, QVector2D d, qreal *s, qreal *t)
59{
60 // lines undefined
61 if ((a.x() == b.x() && a.y() == b.y()) || (c.x() == d.x() && c.y() == d.y()))
62 return false;
63
64 const qreal denom = cross(a, b, c, d);
65
66 // lines are parallel or overlap
67 if (denom == 0)
68 return false;
69
70 if (s != nullptr)
71 *s = cross(c, d, c, a) / denom;
72 if (t != nullptr)
73 *t = cross(a, b, c, a) / denom;
74
75 return true;
76}
77} // namespace
78
79QQuickEllipseShapePrivate::QQuickEllipseShapePrivate() = default;
80
81QQuickEllipseShapePrivate::~QQuickEllipseShapePrivate() = default;
82
83void QQuickEllipseShapePrivate::addLine(QVector2D point)
84{
85 auto line = new QQuickPathLine(path);
86 line->setX(point.x());
87 line->setY(point.y());
88 QQuickPathPrivate::get(path)->appendPathElement(line);
89}
90
91void QQuickEllipseShapePrivate::addArc(QVector2D point, QVector2D arcRadius,
92 QQuickPathArc::ArcDirection dir, bool largeArc)
93{
94 auto arc = new QQuickPathArc(path);
95 arc->setX(point.x());
96 arc->setY(point.y());
97 arc->setRadiusX(arcRadius.x());
98 arc->setRadiusY(arcRadius.y());
99 arc->setDirection(dir);
100 arc->setUseLargeArc(largeArc);
101 QQuickPathPrivate::get(path)->appendPathElement(arc);
102}
103
104qreal QQuickEllipseShapePrivate::getBorderOffset() const
105{
106 if (QQuickEllipseShape::BorderMode::Middle == borderMode)
107 return 0;
108 else if (QQuickEllipseShape::BorderMode::Outside == borderMode)
109 return -path->strokeWidth() * 0.5;
110 return path->strokeWidth() * 0.5f; // inside
111}
112
113void QQuickEllipseShapePrivate::roundCenter(QVector2D center, QVector2D ellipseRadius)
114{
115 const qreal endAngle = arc_angle(startAngle + sweepAngle);
116 const QVector2D endPoint = arc_point(center, ellipseRadius, endAngle);
117 const qreal beginAngle = arc_angle(startAngle);
118 const QVector2D beginPoint = arc_point(center, ellipseRadius, beginAngle);
119
120 const QVector2D AB = endPoint - center;
121 const QVector2D AC = beginPoint - center;
122
123 const qreal a = angle_between_vectors(AB, AC);
124 const qreal halfAngle = std::fabs(a) * 0.5f;
125
126 const qreal maxCornerRadius = (std::min(AB.length(), AC.length()) * 0.5f) * qTan(halfAngle);
127 const qreal corner_radius = std::min(cornerRadius, maxCornerRadius);
128
129 // calculate B and C based on the corner radius
130 const qreal edgeOffset = corner_radius / qTan(halfAngle);
131 const QVector2D B = center + (AB.normalized() * edgeOffset);
132 const QVector2D C = center + (AC.normalized() * edgeOffset);
133
134 // update
135 auto &rc = roundedCorners[RoundedCornerIndex::Center];
136 rc.A = center;
137 rc.B = B;
138 rc.C = C;
139 rc.alpha = a;
140 rc.radius = corner_radius;
141}
142
143void QQuickEllipseShapePrivate::roundBeginEnd(QVector2D center, QVector2D ellipseRadius)
144{
145 qreal deg = 0.0f;
146 const qreal endAngle = startAngle + sweepAngle;
147 bool e_outer = false, b_outer = false, e_inner = false, b_inner = false;
148 while (deg < 45.0f) {
149 deg += 1.0f;
150 if (e_outer && b_outer && e_inner && b_inner)
151 break;
152 if (!b_outer)
153 b_outer = roundOuter(center, ellipseRadius, deg, startAngle, endAngle,
154 RoundedCornerIndex::OuterBegin);
155 if (!e_outer)
156 e_outer = roundOuter(center, ellipseRadius, deg, endAngle, startAngle,
157 RoundedCornerIndex::OuterEnd);
158 if (!e_inner)
159 e_inner = roundInner(center, ellipseRadius, deg, endAngle, startAngle,
160 RoundedCornerIndex::InnerEnd);
161 if (!b_inner)
162 b_inner = roundInner(center, ellipseRadius, deg, startAngle, endAngle,
163 RoundedCornerIndex::InnerBegin);
164 }
165}
166
167bool QQuickEllipseShapePrivate::roundOuter(QVector2D center, QVector2D ellipseRadius, qreal deg,
168 qreal arcAngle1, qreal arcAngle2,
169 RoundedCornerIndex index)
170{
171 bool done = false;
172
173 const qreal arcAngle = arc_angle(arcAngle1);
174 const QVector2D arcPoint = arc_point(center, ellipseRadius, arcAngle);
175
176 const qreal angle = arcAngle1 > arcAngle2 ? arcAngle - deg : arcAngle + deg;
177
178 const QVector2D B = arc_point(center, ellipseRadius, angle); // point on arc
179
180 // calculate tangent vector
181 const QVector2D uV = tangent_ccw(ellipseRadius, angle).normalized();
182 const QVector2D b1 = B + uV;
183
184 qreal s = 0, t = 0;
185 bool res = lines_intersect(center, arcPoint, B, b1, &s, &t);
186 if (res) {
187 const QVector2D A = center + s * (arcPoint - center);
188
189 const QVector2D AB = B - A;
190 const QVector2D AC = center - A;
191
192 const qreal a = angle_between_vectors(AB, AC);
193 const qreal halfAngle = std::fabs(a) * 0.5;
194 const qreal edgeOffset = AB.length();
195
196 const qreal corner_radius = edgeOffset * qTan(halfAngle);
197
198 // constrain by sweep
199 const qreal sweep = std::fabs(arcAngle2 - arcAngle1) * 0.5;
200 const qreal degMax = std::min(sweep, 45.0);
201
202 const QVector2D C = A + AC.normalized() * edgeOffset;
203
204 const qreal ptoc = (arcPoint - C).length();
205 qreal edge = 0;
206 if (innerArcRatio > 0) {
207 const QVector2D ellipseInnerRadius(innerArcRatio * ellipseRadius.x(),
208 innerArcRatio * ellipseRadius.y());
209 const QVector2D innerArcPoint =
210 arc_point(center, ellipseInnerRadius, arcAngle); // point on the inner arc
211 edge = (arcPoint - innerArcPoint).length();
212 } else {
213 edge = (arcPoint - center).length();
214 }
215
216 const qreal diff = std::fabs(corner_radius - cornerRadius); // closest to target radius
217
218 auto &rc = roundedCorners[index];
219
220 bool canUpdate = diff < rc.diff && !(corner_radius > cornerRadius) && !(deg > degMax)
221 && !(rc.radius > corner_radius) && !(ptoc > edge * 0.5f);
222
223 // 1st loop or if constraints are met
224 if (rc.radius == 0 || canUpdate)
225 rc.update(diff, A, B, C, a, corner_radius);
226
227 done =
228 // corner radius is bigger or equal to the target radius
229 corner_radius > cornerRadius
230 || is_equal(corner_radius, cornerRadius, 0.01f)
231 // angle used to define point B is bigger than available sweep
232 || deg > degMax
233 || is_equal(deg, degMax, 0.01f)
234 // the corner radius starts to decline (from previously calculated)
235 || (rc.radius != 0 && rc.radius > corner_radius)
236 // point C is beyond the half of the ellipse edge
237 // (closer to the ellipse center or inner arc point)
238 || (ptoc > edge * 0.5f);
239 }
240
241 return done;
242}
243
244bool QQuickEllipseShapePrivate::roundInner(QVector2D center, QVector2D ellipseRadius, qreal deg,
245 qreal arcAngle1, qreal arcAngle2,
246 RoundedCornerIndex index)
247{
248 // make rounding corner bigger and produces smoother result
249 const qreal smoothFactor = 1.5;
250
251 deg *= smoothFactor;
252
253 bool done = false;
254
255 const QVector2D ellipseInnerRadius(innerArcRatio * ellipseRadius.x(),
256 innerArcRatio * ellipseRadius.y());
257
258 const qreal arcAngle = arc_angle(arcAngle1);
259 const QVector2D innerArcPoint =
260 arc_point(center, ellipseInnerRadius, arcAngle); // point on the inner arc
261
262 const qreal angle = arcAngle1 > arcAngle2 ? arcAngle - deg : arcAngle + deg;
263
264 const QVector2D B = arc_point(center, ellipseInnerRadius, angle); // point on arc
265
266 // calculate tangent vector
267 const QVector2D uV = tangent_ccw(ellipseInnerRadius, angle).normalized();
268 const QVector2D b1 = B + uV;
269
270 qreal s = 0, t = 0;
271 bool res = lines_intersect(center, innerArcPoint, B, b1, &s, &t);
272
273 if (res) {
274 // hit point
275 const QVector2D A = center + s * (innerArcPoint - center); // point on edge
276
277 const auto arcPoint = arc_point(center, ellipseRadius, arcAngle);
278
279 const QVector2D AB = B - A;
280 const QVector2D AC = A - innerArcPoint;
281
282 const qreal a = angle_between_vectors(AB, AC);
283 const qreal halfAngle = std::fabs(a) * 0.5;
284 const qreal edgeOffset = AB.length();
285
286 const qreal corner_radius = edgeOffset * qTan(halfAngle);
287
288 // constrain by sweep
289 const qreal sweep = std::fabs(arcAngle2 - arcAngle1) * 0.5;
290 const qreal degMax = std::min(sweep, 45.0);
291
292 const QVector2D C = A + AC.normalized() * edgeOffset;
293
294 const qreal ptoc = (innerArcPoint - C).length();
295 const qreal edge = (innerArcPoint - arcPoint).length();
296
297 const qreal diff =
298 std::fabs(corner_radius - cornerRadius * smoothFactor); // closest to target radius
299
300 auto &rc = roundedCorners[index];
301
302 bool canUpdate = diff < rc.diff && !(corner_radius > cornerRadius * smoothFactor)
303 && !(deg > degMax) && !(rc.radius > corner_radius) && !(ptoc > edge * 0.5f);
304
305 // 1st loop or if constraints are met
306 if (rc.radius == 0 || canUpdate)
307 rc.update(diff, A, B, C, a, corner_radius);
308
309 done =
310 // corner radius is bigger or equal to the target radius
311 corner_radius > cornerRadius * smoothFactor
312 || is_equal(corner_radius, cornerRadius * smoothFactor, 0.01f)
313 // angle used to define point B is bigger than available sweep
314 || deg > degMax
315 || is_equal(deg, degMax, 0.01f)
316 // the corner radius starts to decline (from previously calculated)
317 || (rc.radius != 0 && rc.radius > corner_radius)
318 // point C is beyond the half of the ellipse edge
319 // (closer to the inner arc end point)
320 || (ptoc > edge * 0.5f);
321 }
322
323 return done;
324}
325
326void QQuickEllipseShapePrivate::drawCenterCorner()
327{
328 auto &rc = roundedCorners[RoundedCornerIndex::Center];
329 path->setStartX(rc.B.x());
330 path->setStartY(rc.B.y());
331
332 addArc(rc.C, QVector2D(rc.radius, rc.radius),
333 rc.alpha < 0 ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
334}
335
336void QQuickEllipseShapePrivate::drawInnerEndCorner()
337{
338 auto &rc = roundedCorners[RoundedCornerIndex::InnerEnd];
339
340 addLine(rc.C);
341
342 addArc(rc.B, QVector2D(rc.radius, rc.radius),
343 rc.alpha > 0 ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
344}
345
346void QQuickEllipseShapePrivate::drawInnerBeginCorner()
347{
348 auto &rc = roundedCorners[RoundedCornerIndex::InnerBegin];
349 path->setStartX(rc.B.x());
350 path->setStartY(rc.B.y());
351
352 addArc(rc.C, QVector2D(rc.radius, rc.radius),
353 rc.alpha < 0 ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
354}
355
356void QQuickEllipseShapePrivate::drawOuterBeginCorner()
357{
358 auto &rc = roundedCorners[RoundedCornerIndex::OuterBegin];
359
360 addLine(rc.C);
361
362 addArc(rc.B, QVector2D(rc.radius, rc.radius),
363 rc.alpha > 0 ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
364}
365
366void QQuickEllipseShapePrivate::drawOuterArcRounded(QVector2D center, QVector2D ellipseRadius)
367{
368 // split outer arc in two parts to avoid issues of the large arc
369 const qreal endAngle = startAngle + sweepAngle;
370
371 const qreal angle = startAngle > endAngle
372 ? arc_angle(startAngle - std::fabs(sweepAngle * 0.5f))
373 : arc_angle(startAngle + std::fabs(sweepAngle * 0.5f));
374 const auto point = arc_point(center, ellipseRadius, angle); // mid point of the arc
375
376 // from begin to mid point
377 addArc(point, ellipseRadius,
378 sweepAngle > 0.0f ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
379
380 auto &rc = roundedCorners[RoundedCornerIndex::OuterEnd];
381
382 // from mid point to end rounded corner
383 addArc(rc.B, ellipseRadius,
384 sweepAngle > 0.0f ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
385
386 // rounded corner
387 addArc(rc.C, QVector2D(rc.radius, rc.radius),
388 rc.alpha < 0 ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
389}
390
391void QQuickEllipseShapePrivate::drawInnerArcRounded(QVector2D center, QVector2D ellipseRadius)
392{
393 // split inner arc in two parts to avoid issues of the large arc
394 const qreal endAngle = startAngle + sweepAngle;
395
396 const QVector2D ellipseInnerRadius(innerArcRatio * ellipseRadius.x(),
397 innerArcRatio * ellipseRadius.y());
398
399 const qreal angle = endAngle > startAngle ? arc_angle(endAngle - std::fabs(sweepAngle * 0.5f))
400 : arc_angle(endAngle + std::fabs(sweepAngle * 0.5f));
401 const auto point = arc_point(center, ellipseInnerRadius, angle); // mid point of the arc
402
403 // from end to mid point
404 addArc(point, ellipseInnerRadius,
405 sweepAngle > 0.0f ? QQuickPathArc::Counterclockwise : QQuickPathArc::Clockwise);
406
407 // from mid point to begin rounded corner
408 auto &rc = roundedCorners[RoundedCornerIndex::InnerBegin];
409 addArc(rc.B, ellipseInnerRadius,
410 sweepAngle > 0.0f ? QQuickPathArc::Counterclockwise : QQuickPathArc::Clockwise);
411}
412
413void QQuickEllipseShapePrivate::drawOuterArc(QVector2D center, QVector2D ellipseRadius)
414{
415 const qreal beginAngle = arc_angle(startAngle);
416 const qreal endAngle = arc_angle(startAngle + sweepAngle);
417
418 const qreal alpha = std::clamp(std::fabs(sweepAngle), 0.0, 359.9);
419 bool isFull = (alpha <= 0.0f || alpha >= 359.0f);
420
421 // QQuickPathArc has some weird behavior when it starts and ends at the same point
422 // leave some gap between the start and the end points in order to avoid it
423 const auto beginPoint = arc_point(center, ellipseRadius, isFull ? 0 : beginAngle);
424 const auto endPoint = arc_point(center, ellipseRadius, isFull ? 359.9 : endAngle);
425
426 path->setStartX(beginPoint.x());
427 path->setStartY(beginPoint.y());
428
429 addArc(endPoint, ellipseRadius,
430 isFull ? QQuickPathArc::Clockwise
431 : (sweepAngle > 0.0f ? QQuickPathArc::Clockwise
432 : QQuickPathArc::Counterclockwise),
433 isFull ? true : alpha > 180.0f);
434
435 // add reverse arc to hide fill color
436 if (qFuzzyCompare(innerArcRatio, 1)) {
437 addArc(beginPoint, ellipseRadius,
438 isFull ? QQuickPathArc::Counterclockwise
439 : (sweepAngle > 0.0f ? QQuickPathArc::Counterclockwise
440 : QQuickPathArc::Clockwise),
441 isFull ? true : alpha > 180.0f);
442 }
443}
444
445void QQuickEllipseShapePrivate::drawFullInnerArc(QVector2D center, QVector2D ellipseRadius)
446{
447 const qreal beginAngle = arc_angle(startAngle);
448
449 auto arc = new QQuickPathAngleArc(path);
450 arc->setCenterX(center.x());
451 arc->setCenterY(center.y());
452 arc->setStartAngle(beginAngle);
453 arc->setRadiusX(innerArcRatio * ellipseRadius.x());
454 arc->setRadiusY(innerArcRatio * ellipseRadius.y());
455 arc->setSweepAngle(sweepAngle);
456 QQuickPathPrivate::get(path)->appendPathElement(arc);
457}
458
459void QQuickEllipseShapePrivate::drawWithInnerRadius(QVector2D center, QVector2D ellipseRadius)
460{
461 drawInnerBeginCorner(); // path starts at the begin rounded corner on the inner arc
462
463 drawOuterBeginCorner(); // path continues to the begin rounded corner on the outer arc
464
465 // outer arc connecting begin and end rounded corners
466 drawOuterArcRounded(center, ellipseRadius);
467
468 // path continues to the end rounded corner on the inner arc
469 drawInnerEndCorner();
470
471 // inner arc connecting end and begin rounded corners
472 drawInnerArcRounded(center, ellipseRadius);
473}
474
475void QQuickEllipseShapePrivate::drawWithoutInnerRadius(QVector2D center, QVector2D ellipseRadius)
476{
477 drawCenterCorner(); // path starts at rounded corner of ellipse center
478
479 drawOuterBeginCorner(); // path continues to the begin rounded corner on the outer arc
480
481 // outer arc connecting begin and end rounded corners
482 drawOuterArcRounded(center, ellipseRadius);
483
484 // path ends at the ellipse's center rounded corner
485 const auto &rc = roundedCorners[RoundedCornerIndex::Center];
486 addLine(rc.B);
487}
488
489void QQuickEllipseShapePrivate::updatePath()
490{
491 const qreal borderOffset = getBorderOffset();
492 const QVector2D center =
493 QVector2D(width.valueBypassingBindings(), height.valueBypassingBindings()) * 0.5f;
494 const QVector2D ellipseRadius = center - QVector2D(borderOffset, borderOffset);
495
496 QQuickPathPrivate::get(path)->clearPathElements(QQuickPathPrivate::DeleteElementPolicy::Delete);
497
498 const qreal alpha = std::clamp(std::fabs(sweepAngle), 0.0, 359.9);
499 const bool isFull = alpha >= 359.0;
500
501 if (qFuzzyCompare(alpha, 0))
502 return;
503
504 // just an arc
505 if (qFuzzyCompare(innerArcRatio, 1)) {
506 drawOuterArc(center, ellipseRadius);
507 return;
508 }
509
510 roundedCorners.reset(); // cleanup old results
511
512 if (innerArcRatio != 0 && isFull) {
513 // this is a donut
514 drawOuterArc(center, ellipseRadius);
515 drawFullInnerArc(center, ellipseRadius);
516 } else if (innerArcRatio != 0 && !isFull) {
517 // this is an outlined arc
518 roundBeginEnd(center, ellipseRadius);
519 drawWithInnerRadius(center, ellipseRadius);
520 } else if (!isFull) {
521 // this is a pie
522 roundCenter(center, ellipseRadius);
523 roundBeginEnd(center, ellipseRadius);
524 drawWithoutInnerRadius(center, ellipseRadius);
525 } else {
526 drawOuterArc(center, ellipseRadius);
527 }
528}
529
530/*!
531 \qmltype EllipseShape
532 \inqmlmodule QtQuick.Shapes.DesignHelpers
533 \brief A shape component that can render an ellipse, an arc, or a pie slice.
534 \since QtQuick 6.10
535
536 The EllipseShape item paints an ellipse, which can be customized to appear
537 as a full ellipse, an arc, or a filled pie slice. Its appearance is
538 controlled by the \l startAngle and \l sweepAngle properties.
539
540 \section1 Basic Ellipse
541 By default, the item renders a full ellipse. The interior is filled with the
542 \l fillColor, and the outline is drawn according to the \l strokeColor, \l
543 strokeWidth, and \l strokeStyle properties.
544
545 \section1 Arc and Pie Slices
546 To create an arc or a pie slice, set the \l startAngle (0-360 degrees) and
547 \l sweepAngle (0-360 degrees) to define the segment of the ellipse to draw.
548
549 \b {Arc Mode}: To create a simple arc (just the outline), set the \l
550 fillColor to \c "transparent". The arc's line style can be customized with
551 \l dashPattern and \l dashOffset.
552
553 \b {Pie Mode}: To create a filled pie slice (a segment connected to the
554 center), simply set the \l fillColor. The outline of the slice will also be
555 stroked.
556
557 \b {Donut Mode}: To create a donut ring (a hollow ellipse), set the
558 \l innerArcRatio to a value between 0.0 and 1.0. This defines the ratio of
559 the inner ellipse's radius to the outer ellipse's radius.
560
561 The area inside the stroke is painted using either a solid fill color,
562 specified using the \l fillColor property, or a gradient, defined using one
563 of the \l ShapeGradient subtypes and set using the \l fillGradient
564 property. If both a color and a gradient are specified, the gradient is
565 used.
566
567 An optional border can be added to an ellipse with its own color and
568 thickness by setting the \l strokeColor and \l strokeWidth properties.
569 Setting the color to \c transparent creates a border without a fill color.
570
571 Ellipse can be drawn with rounded corners using the \l cornerRadius
572 property. The default value of the \l cornerRadius is 10 degrees.
573
574 EllipseShape's default value for \l {QtQuick.Shapes::Shape::preferredRendererType} is
575 \c Shape.CurveRenderer.
576
577 \section1 Example Usage
578
579 \snippet ellipseshape.qml ellipseShape
580
581 \image path-ellipseshape.png
582*/
583QQuickEllipseShape::QQuickEllipseShape(QQuickItem *parent)
584 : QQuickShape(*(new QQuickEllipseShapePrivate), parent)
585{
586 Q_D(QQuickEllipseShape);
587
588 setPreferredRendererType(CurveRenderer);
589
590 setWidth(200);
591 setHeight(200);
592
593 d->path = new QQuickShapePath(this);
594 d->path->setParent(this);
595 d->path->setStrokeWidth(1);
596 d->path->setStrokeColor(QColorConstants::Black);
597 d->path->setFillColor(QColorConstants::White);
598
599 d->sp.append(d->path);
600 d->path->setParent(this);
601 d->extra.value().resourcesList.append(d->path);
602
603 connect(d->path, &QQuickShapePath::strokeColorChanged, this, &QQuickEllipseShape::strokeColorChanged);
604 connect(d->path, &QQuickShapePath::strokeWidthChanged, this, &QQuickEllipseShape::strokeWidthChanged);
605 connect(d->path, &QQuickShapePath::fillColorChanged, this, &QQuickEllipseShape::fillColorChanged);
606 connect(d->path, &QQuickShapePath::joinStyleChanged, this, &QQuickEllipseShape::joinStyleChanged);
607 connect(d->path, &QQuickShapePath::capStyleChanged, this, &QQuickEllipseShape::capStyleChanged);
608 connect(d->path, &QQuickShapePath::strokeStyleChanged, this, &QQuickEllipseShape::strokeStyleChanged);
609 connect(d->path, &QQuickShapePath::dashOffsetChanged, this, &QQuickEllipseShape::dashOffsetChanged);
610 connect(d->path, &QQuickShapePath::dashPatternChanged, this, &QQuickEllipseShape::dashPatternChanged);
611 connect(d->path, &QQuickShapePath::fillItemChanged, this, &QQuickEllipseShape::fillItemChanged);
612 connect(d->path, &QQuickShapePath::fillGradientChanged, this, &QQuickEllipseShape::fillGradientChanged);
613 connect(d->path, &QQuickShapePath::fillRuleChanged, this, &QQuickEllipseShape::fillRuleChanged);
614}
615
616QQuickEllipseShape::~QQuickEllipseShape() = default;
617
618/*!
619 \qmlproperty real QtQuick.Shapes.DesignHelpers::EllipseShape::sweepAngle
620
621 The angular extent in degrees to be drawn from the \l startAngle.
622
623 If set to positive value, the arc is drawn in clockwise direction.
624 If set to negative value, the arc is drawn in counter-clockwise direction.
625
626 The default value is \c 360.
627*/
628qreal QQuickEllipseShape::sweepAngle() const
629{
630 Q_D(const QQuickEllipseShape);
631 return d->sweepAngle;
632}
633
634void QQuickEllipseShape::setSweepAngle(qreal sweepAngle)
635{
636 Q_D(QQuickEllipseShape);
637 if (qFuzzyCompare(d->sweepAngle, sweepAngle))
638 return;
639 d->sweepAngle = sweepAngle;
640 d->updatePath();
641 emit sweepAngleChanged();
642}
643
644/*!
645 \qmlproperty real QtQuick.Shapes.DesignHelpers::EllipseShape::startAngle
646
647 The property defines the starting angle in degrees from which to begin
648 drawing the ellipse.
649
650 0 degrees points to the top. Angle increases in clockwise direction.
651
652 The default value is \c 0.
653*/
654qreal QQuickEllipseShape::startAngle() const
655{
656 Q_D(const QQuickEllipseShape);
657 return d->startAngle;
658}
659
660void QQuickEllipseShape::setStartAngle(qreal startAngle)
661{
662 Q_D(QQuickEllipseShape);
663 if (qFuzzyCompare(d->startAngle, startAngle))
664 return;
665 d->startAngle = startAngle;
666 d->updatePath();
667 emit startAngleChanged();
668}
669
670/*!
671 \include shapepath.qdocinc {dashOffset-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
672*/
673
674qreal QQuickEllipseShape::dashOffset() const
675{
676 Q_D(const QQuickEllipseShape);
677 return d->path->dashOffset();
678}
679
680void QQuickEllipseShape::setDashOffset(qreal offset)
681{
682 Q_D(QQuickEllipseShape);
683 d->path->setDashOffset(offset);
684}
685
686/*!
687 \qmlproperty real QtQuick.Shapes.DesignHelpers::EllipseShape::cornerRadius
688
689 Controls the rounding of corners where the radial lines meet the elliptical
690 arcs. For pie segments, this rounds the connection to the outer arc. For
691 donut segments, this also rounds the connections to both inner and outer arcs.
692
693 The default value is \c 10.
694*/
695qreal QQuickEllipseShape::cornerRadius() const
696{
697 Q_D(const QQuickEllipseShape);
698 return d->cornerRadius;
699}
700
701void QQuickEllipseShape::setCornerRadius(qreal cornerRadius)
702{
703 Q_D(QQuickEllipseShape);
704 if (qFuzzyCompare(d->cornerRadius, cornerRadius))
705 return;
706 d->cornerRadius = cornerRadius;
707 d->updatePath();
708 emit cornerRadiusChanged();
709}
710
711/*!
712 \qmlproperty real QtQuick.Shapes.DesignHelpers::EllipseShape::innerArcRatio
713
714 This property defines the ratio between the inner and outer arcs.
715
716 Value range is between 0.0 and 1.0. Setting the value to 0.0 will cause
717 the inner arc to collapse toward the center, drawing a solid filled
718 ellipse. Setting the value to 1.0 makes the inner arc the same size as the
719 outer ellipse, resulting in just an arc. Values between 0.0 and 1.0 create
720 hollow elliptical rings.
721
722 The default value is \c 0.
723*/
724qreal QQuickEllipseShape::innerArcRatio() const
725{
726 Q_D(const QQuickEllipseShape);
727 return d->innerArcRatio;
728}
729
730void QQuickEllipseShape::setInnerArcRatio(qreal innerArcRatio)
731{
732 Q_D(QQuickEllipseShape);
733 if (qFuzzyCompare(d->innerArcRatio, innerArcRatio))
734 return;
735 d->innerArcRatio = innerArcRatio;
736 d->updatePath();
737 emit innerArcRatioChanged();
738}
739
740/*!
741 \qmlproperty real QtQuick.Shapes.DesignHelpers::EllipseShape::strokeWidth
742
743 This property holds the stroke width.
744
745 When set to a negative value, no stroking occurs.
746
747 The default value is \c 1.
748*/
749
750qreal QQuickEllipseShape::strokeWidth() const
751{
752 Q_D(const QQuickEllipseShape);
753 return d->path->strokeWidth();
754}
755
756void QQuickEllipseShape::setStrokeWidth(qreal width)
757{
758 Q_D(QQuickEllipseShape);
759 d->path->setStrokeWidth(width);
760}
761
762/*!
763 \qmlproperty color QtQuick.Shapes.DesignHelpers::EllipseShape::fillColor
764
765 This property holds the fill color.
766
767 When set to \c transparent, no filling occurs.
768
769 The default value is \c "white".
770
771 \note If either \l fillGradient is set to something other than \c null, it
772 will be used instead of \c fillColor.
773*/
774
775QColor QQuickEllipseShape::fillColor() const
776{
777 Q_D(const QQuickEllipseShape);
778 return d->path->fillColor();
779}
780
781void QQuickEllipseShape::setFillColor(const QColor &color)
782{
783 Q_D(QQuickEllipseShape);
784 d->path->setFillColor(color);
785}
786
787/*!
788 \qmlproperty color QtQuick.Shapes.DesignHelpers::EllipseShape::strokeColor
789
790 This property holds the stroking color.
791
792 When set to \c transparent, no stroking occurs.
793
794 The default value is \c "black".
795*/
796
797QColor QQuickEllipseShape::strokeColor() const
798{
799 Q_D(const QQuickEllipseShape);
800 return d->path->strokeColor();
801}
802
803void QQuickEllipseShape::setStrokeColor(const QColor &color)
804{
805 Q_D(QQuickEllipseShape);
806 d->path->setStrokeColor(color);
807}
808
809/*!
810 \include shapepath.qdocinc {capStyle-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
811*/
812
813QQuickShapePath::CapStyle QQuickEllipseShape::capStyle() const
814{
815 Q_D(const QQuickEllipseShape);
816 return d->path->capStyle();
817}
818
819void QQuickEllipseShape::setCapStyle(QQuickShapePath::CapStyle style)
820{
821 Q_D(QQuickEllipseShape);
822 d->path->setCapStyle(style);
823}
824
825/*!
826 \include shapepath.qdocinc {joinStyle-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
827*/
828
829QQuickShapePath::JoinStyle QQuickEllipseShape::joinStyle() const
830{
831 Q_D(const QQuickEllipseShape);
832 return d->path->joinStyle();
833}
834
835void QQuickEllipseShape::setJoinStyle(QQuickShapePath::JoinStyle style)
836{
837 Q_D(QQuickEllipseShape);
838 d->path->setJoinStyle(style);
839}
840
841/*!
842 \include shapepath.qdocinc {strokeStyle-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
843*/
844
845QQuickShapePath::StrokeStyle QQuickEllipseShape::strokeStyle() const
846{
847 Q_D(const QQuickEllipseShape);
848 return d->path->strokeStyle();
849}
850
851void QQuickEllipseShape::setStrokeStyle(QQuickShapePath::StrokeStyle style)
852{
853 Q_D(QQuickEllipseShape);
854 d->path->setStrokeStyle(style);
855}
856
857/*!
858 \include shapepath.qdocinc {fillRule-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
859*/
860
861QQuickShapePath::FillRule QQuickEllipseShape::fillRule() const
862{
863 Q_D(const QQuickEllipseShape);
864 return d->path->fillRule();
865}
866
867void QQuickEllipseShape::setFillRule(QQuickShapePath::FillRule fillRule)
868{
869 Q_D(QQuickEllipseShape);
870 d->path->setFillRule(fillRule);
871}
872
873/*!
874 \include shapepath.qdocinc {dashPattern-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
875*/
876
877QList<qreal> QQuickEllipseShape::dashPattern() const
878{
879 Q_D(const QQuickEllipseShape);
880 return d->path->dashPattern();
881}
882
883void QQuickEllipseShape::setDashPattern(const QList<qreal> &array)
884{
885 Q_D(QQuickEllipseShape);
886 d->path->setDashPattern(array);
887}
888
889/*!
890 \qmlproperty ShapeGradient QtQuick.Shapes.DesignHelpers::EllipseShape::fillGradient
891
892 The fillGradient of the ellipse fill color.
893
894 By default, no fillGradient is enabled and the value is null. In this case, the
895 fill uses a solid color based on the value of \l fillColor.
896
897 When set, \l fillColor is ignored and filling is done using one of the
898 \l ShapeGradient subtypes.
899
900 \note The \l Gradient type cannot be used here. Rather, prefer using one of
901 the advanced subtypes, like \l LinearGradient.
902*/
903QQuickShapeGradient *QQuickEllipseShape::fillGradient() const
904{
905 Q_D(const QQuickEllipseShape);
906 return d->path->fillGradient();
907}
908
909void QQuickEllipseShape::setFillGradient(QQuickShapeGradient *fillGradient)
910{
911 Q_D(QQuickEllipseShape);
912 d->path->setFillGradient(fillGradient);
913}
914
915void QQuickEllipseShape::resetFillGradient()
916{
917 setFillGradient(nullptr);
918}
919
920/*!
921 \qmlproperty enumeration QtQuick.Shapes.DesignHelpers::EllipseShape::borderMode
922
923 The \l borderMode property determines where the border is drawn along the
924 edge of the ellipse.
925
926 \value EllipseShape.Inside
927 The border is drawn along the inside edge of the item and does not
928 affect the item width.
929
930 This is the default value.
931 \value EllipseShape.Middle
932 The border is drawn over the edge of the item and does not
933 affect the item width.
934 \value EllipseShape.Outside
935 The border is drawn along the outside edge of the item and increases
936 the item width by the value of \l strokeWidth.
937
938 \sa strokeWidth
939*/
940QQuickEllipseShape::BorderMode QQuickEllipseShape::borderMode() const
941{
942 Q_D(const QQuickEllipseShape);
943 return d->borderMode;
944}
945
946void QQuickEllipseShape::setBorderMode(BorderMode borderMode)
947{
948 Q_D(QQuickEllipseShape);
949 if (borderMode == d->borderMode)
950 return;
951 d->borderMode = borderMode;
952 d->updatePath();
953 emit borderModeChanged();
954}
955
956void QQuickEllipseShape::resetBorderMode()
957{
958 setBorderMode(BorderMode::Inside);
959}
960
961QQuickItem *QQuickEllipseShape::fillItem() const
962{
963 Q_D(const QQuickEllipseShape);
964 return d->path->fillItem();
965}
966
967void QQuickEllipseShape::setFillItem(QQuickItem *newFillItem)
968{
969 Q_D(QQuickEllipseShape);
970 d->path->setFillItem(newFillItem);
971}
972
973void QQuickEllipseShape::itemChange(ItemChange change, const ItemChangeData &value)
974{
975 Q_D(QQuickEllipseShape);
976
977 if (d->path)
978 d->updatePath();
979
980 QQuickItem::itemChange(change, value);
981}
982
983QT_END_NAMESPACE
984
985#include "moc_qquickellipseshape_p.cpp"
qreal angle_between_vectors(QVector2D a, QVector2D b)
qreal cross(QVector2D a, QVector2D b)
qreal arc_angle(qreal angle)
QVector2D tangent_ccw(QVector2D radius, qreal angle)
bool lines_intersect(QVector2D a, QVector2D b, QVector2D c, QVector2D d, qreal *s, qreal *t)
qreal cross(QVector2D a, QVector2D b, QVector2D c, QVector2D d)
bool is_equal(qreal a, qreal b, qreal epsilon=DBL_EPSILON)
QVector2D arc_point(QVector2D center, QVector2D radius, qreal angle)