// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qquickellipseshape_p.h"
#include "qquickellipseshape_p_p.h"
#include <algorithm>
#include <QtMath>

QT_BEGIN_NAMESPACE

namespace {
inline bool is_equal(qreal a, qreal b, qreal epsilon = DBL_EPSILON)
{
    return std::fabs(a - b) <= epsilon;
}

inline qreal arc_angle(qreal angle)
{
    return angle - 90;
}

inline QVector2D arc_point(QVector2D center, QVector2D radius, qreal angle)
{
    return QVector2D(center.x() + radius.x() * qCos(qDegreesToRadians(angle)),
                     center.y() + radius.y() * qSin(qDegreesToRadians(angle)));
}

// counter-clockwise
inline QVector2D tangent_ccw(QVector2D radius, qreal angle)
{
    return QVector2D(-radius.x() * qSin(qDegreesToRadians(angle)),
                     radius.y() * qCos(qDegreesToRadians(angle)));
}

inline qreal cross(QVector2D a, QVector2D b)
{
    return a.x() * b.y() - a.y() * b.x();
}

// cross product of two vectors defined by points A->B x C->D
inline qreal cross(QVector2D a, QVector2D b, QVector2D c, QVector2D d)
{
    return cross(b - a, d - c);
}

qreal angle_between_vectors(QVector2D a, QVector2D b)
{
    const QVector2D uA = a.normalized();
    const QVector2D uB = b.normalized();
    const qreal angle = qAtan2(cross(uA, uB), QVector2D::dotProduct(uA, uB));
    if (std::fabs(angle) < FLT_EPSILON)
        return 0.0f;
    return angle;
}

// the intersection point can be calculated as C + T * (D - C) or A + S * (B - A)
bool lines_intersect(QVector2D a, QVector2D b, QVector2D c, QVector2D d, qreal *s, qreal *t)
{
    // lines undefined
    if ((a.x() == b.x() && a.y() == b.y()) || (c.x() == d.x() && c.y() == d.y()))
        return false;

    const qreal denom = cross(a, b, c, d);

    // lines are parallel or overlap
    if (denom == 0)
        return false;

    if (s != nullptr)
        *s = cross(c, d, c, a) / denom;
    if (t != nullptr)
        *t = cross(a, b, c, a) / denom;

    return true;
}
} // namespace

QQuickEllipseShapePrivate::QQuickEllipseShapePrivate() = default;

QQuickEllipseShapePrivate::~QQuickEllipseShapePrivate() = default;

void QQuickEllipseShapePrivate::addLine(QVector2D point)
{
    auto line = new QQuickPathLine(path);
    line->setX(point.x());
    line->setY(point.y());
    QQuickPathPrivate::get(path)->appendPathElement(line);
}

void QQuickEllipseShapePrivate::addArc(QVector2D point, QVector2D arcRadius,
                                       QQuickPathArc::ArcDirection dir, bool largeArc)
{
    auto arc = new QQuickPathArc(path);
    arc->setX(point.x());
    arc->setY(point.y());
    arc->setRadiusX(arcRadius.x());
    arc->setRadiusY(arcRadius.y());
    arc->setDirection(dir);
    arc->setUseLargeArc(largeArc);
    QQuickPathPrivate::get(path)->appendPathElement(arc);
}

qreal QQuickEllipseShapePrivate::getBorderOffset() const
{
    if (QQuickEllipseShape::BorderMode::Middle == borderMode)
        return 0;
    else if (QQuickEllipseShape::BorderMode::Outside == borderMode)
        return -path->strokeWidth() * 0.5;
    return path->strokeWidth() * 0.5f; // inside
}

void QQuickEllipseShapePrivate::roundCenter(QVector2D center, QVector2D ellipseRadius)
{
    const qreal endAngle = arc_angle(startAngle + sweepAngle);
    const QVector2D endPoint = arc_point(center, ellipseRadius, endAngle);
    const qreal beginAngle = arc_angle(startAngle);
    const QVector2D beginPoint = arc_point(center, ellipseRadius, beginAngle);

    const QVector2D AB = endPoint - center;
    const QVector2D AC = beginPoint - center;

    const qreal a = angle_between_vectors(AB, AC);
    const qreal halfAngle = std::fabs(a) * 0.5f;

    const qreal maxCornerRadius = (std::min(AB.length(), AC.length()) * 0.5f) * qTan(halfAngle);
    const qreal corner_radius = std::min(cornerRadius, maxCornerRadius);

    // calculate B and C based on the corner radius
    const qreal edgeOffset = corner_radius / qTan(halfAngle);
    const QVector2D B = center + (AB.normalized() * edgeOffset);
    const QVector2D C = center + (AC.normalized() * edgeOffset);

    // update
    auto &rc = roundedCorners[RoundedCornerIndex::Center];
    rc.A = center;
    rc.B = B;
    rc.C = C;
    rc.alpha = a;
    rc.radius = corner_radius;
}

void QQuickEllipseShapePrivate::roundBeginEnd(QVector2D center, QVector2D ellipseRadius)
{
    qreal deg = 0.0f;
    const qreal endAngle = startAngle + sweepAngle;
    bool e_outer = false, b_outer = false, e_inner = false, b_inner = false;
    while (deg < 45.0f) {
        deg += 1.0f;
        if (e_outer && b_outer && e_inner && b_inner)
            break;
        if (!b_outer)
            b_outer = roundOuter(center, ellipseRadius, deg, startAngle, endAngle,
                                 RoundedCornerIndex::OuterBegin);
        if (!e_outer)
            e_outer = roundOuter(center, ellipseRadius, deg, endAngle, startAngle,
                                 RoundedCornerIndex::OuterEnd);
        if (!e_inner)
            e_inner = roundInner(center, ellipseRadius, deg, endAngle, startAngle,
                                 RoundedCornerIndex::InnerEnd);
        if (!b_inner)
            b_inner = roundInner(center, ellipseRadius, deg, startAngle, endAngle,
                                 RoundedCornerIndex::InnerBegin);
    }
}

bool QQuickEllipseShapePrivate::roundOuter(QVector2D center, QVector2D ellipseRadius, qreal deg,
                                           qreal arcAngle1, qreal arcAngle2,
                                           RoundedCornerIndex index)
{
    bool done = false;

    const qreal arcAngle = arc_angle(arcAngle1);
    const QVector2D arcPoint = arc_point(center, ellipseRadius, arcAngle);

    const qreal angle = arcAngle1 > arcAngle2 ? arcAngle - deg : arcAngle + deg;

    const QVector2D B = arc_point(center, ellipseRadius, angle); // point on arc

    // calculate tangent vector
    const QVector2D uV = tangent_ccw(ellipseRadius, angle).normalized();
    const QVector2D b1 = B + uV;

    qreal s = 0, t = 0;
    bool res = lines_intersect(center, arcPoint, B, b1, &s, &t);
    if (res) {
        const QVector2D A = center + s * (arcPoint - center);

        const QVector2D AB = B - A;
        const QVector2D AC = center - A;

        const qreal a = angle_between_vectors(AB, AC);
        const qreal halfAngle = std::fabs(a) * 0.5;
        const qreal edgeOffset = AB.length();

        const qreal corner_radius = edgeOffset * qTan(halfAngle);

        // constrain by sweep
        const qreal sweep = std::fabs(arcAngle2 - arcAngle1) * 0.5;
        const qreal degMax = std::min(sweep, 45.0);

        const QVector2D C = A + AC.normalized() * edgeOffset;

        const qreal ptoc = (arcPoint - C).length();
        qreal edge = 0;
        if (innerArcRatio > 0) {
            const QVector2D ellipseInnerRadius(innerArcRatio * ellipseRadius.x(),
                                               innerArcRatio * ellipseRadius.y());
            const QVector2D innerArcPoint =
                    arc_point(center, ellipseInnerRadius, arcAngle); // point on the inner arc
            edge = (arcPoint - innerArcPoint).length();
        } else {
            edge = (arcPoint - center).length();
        }

        const qreal diff = std::fabs(corner_radius - cornerRadius); // closest to target radius

        auto &rc = roundedCorners[index];

        bool canUpdate = diff < rc.diff && !(corner_radius > cornerRadius) && !(deg > degMax)
                && !(rc.radius > corner_radius) && !(ptoc > edge * 0.5f);

        // 1st loop or if constraints are met
        if (rc.radius == 0 || canUpdate)
            rc.update(diff, A, B, C, a, corner_radius);

        done =
                // corner radius is bigger or equal to the target radius
                corner_radius > cornerRadius
                || is_equal(corner_radius, cornerRadius, 0.01f)
                // angle used to define point B is bigger than available sweep
                || deg > degMax
                || is_equal(deg, degMax, 0.01f)
                // the corner radius starts to decline (from previously calculated)
                || (rc.radius != 0 && rc.radius > corner_radius)
                // point C is beyond the half of the ellipse edge
                // (closer to the ellipse center or inner arc point)
                || (ptoc > edge * 0.5f);
    }

    return done;
}

bool QQuickEllipseShapePrivate::roundInner(QVector2D center, QVector2D ellipseRadius, qreal deg,
                                           qreal arcAngle1, qreal arcAngle2,
                                           RoundedCornerIndex index)
{
    // make rounding corner bigger and produces smoother result
    const qreal smoothFactor = 1.5;

    deg *= smoothFactor;

    bool done = false;

    const QVector2D ellipseInnerRadius(innerArcRatio * ellipseRadius.x(),
                                       innerArcRatio * ellipseRadius.y());

    const qreal arcAngle = arc_angle(arcAngle1);
    const QVector2D innerArcPoint =
            arc_point(center, ellipseInnerRadius, arcAngle); // point on the inner arc

    const qreal angle = arcAngle1 > arcAngle2 ? arcAngle - deg : arcAngle + deg;

    const QVector2D B = arc_point(center, ellipseInnerRadius, angle); // point on arc

    // calculate tangent vector
    const QVector2D uV = tangent_ccw(ellipseInnerRadius, angle).normalized();
    const QVector2D b1 = B + uV;

    qreal s = 0, t = 0;
    bool res = lines_intersect(center, innerArcPoint, B, b1, &s, &t);

    if (res) {
        // hit point
        const QVector2D A = center + s * (innerArcPoint - center); // point on edge

        const auto arcPoint = arc_point(center, ellipseRadius, arcAngle);

        const QVector2D AB = B - A;
        const QVector2D AC = A - innerArcPoint;

        const qreal a = angle_between_vectors(AB, AC);
        const qreal halfAngle = std::fabs(a) * 0.5;
        const qreal edgeOffset = AB.length();

        const qreal corner_radius = edgeOffset * qTan(halfAngle);

        // constrain by sweep
        const qreal sweep = std::fabs(arcAngle2 - arcAngle1) * 0.5;
        const qreal degMax = std::min(sweep, 45.0);

        const QVector2D C = A + AC.normalized() * edgeOffset;

        const qreal ptoc = (innerArcPoint - C).length();
        const qreal edge = (innerArcPoint - arcPoint).length();

        const qreal diff =
                std::fabs(corner_radius - cornerRadius * smoothFactor); // closest to target radius

        auto &rc = roundedCorners[index];

        bool canUpdate = diff < rc.diff && !(corner_radius > cornerRadius * smoothFactor)
                && !(deg > degMax) && !(rc.radius > corner_radius) && !(ptoc > edge * 0.5f);

        // 1st loop or if constraints are met
        if (rc.radius == 0 || canUpdate)
            rc.update(diff, A, B, C, a, corner_radius);

        done =
                // corner radius is bigger or equal to the target radius
                corner_radius > cornerRadius * smoothFactor
                || is_equal(corner_radius, cornerRadius * smoothFactor, 0.01f)
                // angle used to define point B is bigger than available sweep
                || deg > degMax
                || is_equal(deg, degMax, 0.01f)
                // the corner radius starts to decline (from previously calculated)
                || (rc.radius != 0 && rc.radius > corner_radius)
                // point C is beyond the half of the ellipse edge
                // (closer to the inner arc end point)
                || (ptoc > edge * 0.5f);
    }

    return done;
}

void QQuickEllipseShapePrivate::drawCenterCorner()
{
    auto &rc = roundedCorners[RoundedCornerIndex::Center];
    path->setStartX(rc.B.x());
    path->setStartY(rc.B.y());

    addArc(rc.C, QVector2D(rc.radius, rc.radius),
           rc.alpha < 0 ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
}

void QQuickEllipseShapePrivate::drawInnerEndCorner()
{
    auto &rc = roundedCorners[RoundedCornerIndex::InnerEnd];

    addLine(rc.C);

    addArc(rc.B, QVector2D(rc.radius, rc.radius),
           rc.alpha > 0 ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
}

void QQuickEllipseShapePrivate::drawInnerBeginCorner()
{
    auto &rc = roundedCorners[RoundedCornerIndex::InnerBegin];
    path->setStartX(rc.B.x());
    path->setStartY(rc.B.y());

    addArc(rc.C, QVector2D(rc.radius, rc.radius),
           rc.alpha < 0 ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
}

void QQuickEllipseShapePrivate::drawOuterBeginCorner()
{
    auto &rc = roundedCorners[RoundedCornerIndex::OuterBegin];

    addLine(rc.C);

    addArc(rc.B, QVector2D(rc.radius, rc.radius),
           rc.alpha > 0 ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
}

void QQuickEllipseShapePrivate::drawOuterArcRounded(QVector2D center, QVector2D ellipseRadius)
{
    // split outer arc in two parts to avoid issues of the large arc
    const qreal endAngle = startAngle + sweepAngle;

    const qreal angle = startAngle > endAngle
            ? arc_angle(startAngle - std::fabs(sweepAngle * 0.5f))
            : arc_angle(startAngle + std::fabs(sweepAngle * 0.5f));
    const auto point = arc_point(center, ellipseRadius, angle); // mid point of the arc

    // from begin to mid point
    addArc(point, ellipseRadius,
           sweepAngle > 0.0f ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);

    auto &rc = roundedCorners[RoundedCornerIndex::OuterEnd];

    // from mid point to end rounded corner
    addArc(rc.B, ellipseRadius,
           sweepAngle > 0.0f ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);

    // rounded corner
    addArc(rc.C, QVector2D(rc.radius, rc.radius),
           rc.alpha < 0 ? QQuickPathArc::Clockwise : QQuickPathArc::Counterclockwise);
}

void QQuickEllipseShapePrivate::drawInnerArcRounded(QVector2D center, QVector2D ellipseRadius)
{
    // split inner arc in two parts to avoid issues of the large arc
    const qreal endAngle = startAngle + sweepAngle;

    const QVector2D ellipseInnerRadius(innerArcRatio * ellipseRadius.x(),
                                       innerArcRatio * ellipseRadius.y());

    const qreal angle = endAngle > startAngle ? arc_angle(endAngle - std::fabs(sweepAngle * 0.5f))
                                              : arc_angle(endAngle + std::fabs(sweepAngle * 0.5f));
    const auto point = arc_point(center, ellipseInnerRadius, angle); // mid point of the arc

    // from end to mid point
    addArc(point, ellipseInnerRadius,
           sweepAngle > 0.0f ? QQuickPathArc::Counterclockwise : QQuickPathArc::Clockwise);

    // from mid point to begin rounded corner
    auto &rc = roundedCorners[RoundedCornerIndex::InnerBegin];
    addArc(rc.B, ellipseInnerRadius,
           sweepAngle > 0.0f ? QQuickPathArc::Counterclockwise : QQuickPathArc::Clockwise);
}

void QQuickEllipseShapePrivate::drawOuterArc(QVector2D center, QVector2D ellipseRadius)
{
    const qreal beginAngle = arc_angle(startAngle);
    const qreal endAngle = arc_angle(startAngle + sweepAngle);

    const qreal alpha = std::clamp(std::fabs(sweepAngle), 0.0, 359.9);
    bool isFull = (alpha <= 0.0f || alpha >= 359.0f);

    // QQuickPathArc has some weird behavior when it starts and ends at the same point
    // leave some gap between the start and the end points in order to avoid it
    const auto beginPoint = arc_point(center, ellipseRadius, isFull ? 0 : beginAngle);
    const auto endPoint = arc_point(center, ellipseRadius, isFull ? 359.9 : endAngle);

    path->setStartX(beginPoint.x());
    path->setStartY(beginPoint.y());

    addArc(endPoint, ellipseRadius,
           isFull ? QQuickPathArc::Clockwise
                  : (sweepAngle > 0.0f ? QQuickPathArc::Clockwise
                                       : QQuickPathArc::Counterclockwise),
           isFull ? true : alpha > 180.0f);
}

void QQuickEllipseShapePrivate::drawFullInnerArc(QVector2D center, QVector2D ellipseRadius)
{
    const qreal beginAngle = arc_angle(startAngle);

    auto arc = new QQuickPathAngleArc(path);
    arc->setCenterX(center.x());
    arc->setCenterY(center.y());
    arc->setStartAngle(beginAngle);
    arc->setRadiusX(innerArcRatio * ellipseRadius.x());
    arc->setRadiusY(innerArcRatio * ellipseRadius.y());
    arc->setSweepAngle(sweepAngle);
    QQuickPathPrivate::get(path)->appendPathElement(arc);
}

void QQuickEllipseShapePrivate::drawWithInnerRadius(QVector2D center, QVector2D ellipseRadius)
{
    drawInnerBeginCorner(); // path starts at the begin rounded corner on the inner arc

    drawOuterBeginCorner(); // path continues to the begin rounded corner on the outer arc

    // outer arc connecting begin and end rounded corners
    drawOuterArcRounded(center, ellipseRadius);

    // path continues to the end rounded corner on the inner arc
    drawInnerEndCorner();

    // inner arc connecting end and begin rounded corners
    drawInnerArcRounded(center, ellipseRadius);
}

void QQuickEllipseShapePrivate::drawWithoutInnerRadius(QVector2D center, QVector2D ellipseRadius)
{
    drawCenterCorner(); // path starts at rounded corner of ellipse center

    drawOuterBeginCorner(); // path continues to the begin rounded corner on the outer arc

    // outer arc connecting begin and end rounded corners
    drawOuterArcRounded(center, ellipseRadius);

    // path ends at the ellipse's center rounded corner
    const auto &rc = roundedCorners[RoundedCornerIndex::Center];
    addLine(rc.B);
}

void QQuickEllipseShapePrivate::updatePath()
{
    const qreal borderOffset = getBorderOffset();
    const QVector2D center =
            QVector2D(width.valueBypassingBindings(), height.valueBypassingBindings()) * 0.5f;
    const QVector2D ellipseRadius = center - QVector2D(borderOffset, borderOffset);

    QQuickPathPrivate::get(path)->clearPathElements(QQuickPathPrivate::DeleteElementPolicy::Delete);

    const qreal alpha = std::clamp(std::fabs(sweepAngle), 0.0, 359.9);
    const bool isFull = alpha >= 359.0;

    if (qFuzzyCompare(alpha, 0))
        return;

    // just an arc
    if (qFuzzyCompare(innerArcRatio, 1) || (hideLine && qFuzzyCompare(innerArcRatio, 0))) {
        drawOuterArc(center, ellipseRadius);
        return;
    }

    roundedCorners.reset(); // cleanup old results

    if (innerArcRatio != 0 && isFull) {
        // this is a donut
        drawOuterArc(center, ellipseRadius);
        drawFullInnerArc(center, ellipseRadius);
    } else if (innerArcRatio != 0 && !isFull) {
        // this is an outlined arc
        roundBeginEnd(center, ellipseRadius);
        drawWithInnerRadius(center, ellipseRadius);
    } else if (!isFull) {
        // this is a pie
        roundCenter(center, ellipseRadius);
        roundBeginEnd(center, ellipseRadius);
        drawWithoutInnerRadius(center, ellipseRadius);
    } else {
        drawOuterArc(center, ellipseRadius);
    }
}

/*!
    \qmltype EllipseShape
    \inqmlmodule QtQuick.Shapes.DesignHelpers
    \brief A shape component that can render an ellipse, an arc, or a pie slice.
    \since QtQuick 6.10

    The EllipseShape item paints an ellipse, which can be customized to appear
    as a full ellipse, an arc, or a filled pie slice. Its appearance is
    controlled by the \l startAngle and \l sweepAngle properties.

    \section1 Basic Ellipse
    By default, the item renders a full ellipse. The interior is filled with the
    \l fillColor, and the outline is drawn according to the \l strokeColor, \l
    strokeWidth, and \l strokeStyle properties.

    \section1 Arc and Pie Slices
    To create an arc or a pie slice, set the \l startAngle (0-360 degrees) and
    \l sweepAngle (0-360 degrees) to define the segment of the ellipse to draw.

    \b {Arc Mode}: To create a simple arc (just the outline), set the \l
    fillColor to \c "transparent". The arc's line style can be customized with
    \l dashPattern and \l dashOffset.

    \b {Pie Mode}: To create a filled pie slice (a segment connected to the
    center), simply set the \l fillColor. The outline of the slice will also be
    stroked.

    \b {Donut Mode}: To create a donut ring (a hollow ellipse), set the
    \l innerArcRatio to a value between 0.0 and 1.0. This defines the ratio of
    the inner ellipse's radius to the outer ellipse's radius.

    The area inside the stroke is painted using either a solid fill color,
    specified using the \l fillColor property, or a gradient, defined using one
    of the \l ShapeGradient subtypes and set using the \l fillGradient
    property. If both a color and a gradient are specified, the gradient is
    used.

    An optional border can be added to an ellipse with its own color and
    thickness by setting the \l strokeColor and \l strokeWidth properties.
    Setting the color to \c transparent creates a border without a fill color.

    Ellipse can be drawn with rounded corners using the \l cornerRadius
    property. The default value of the \l cornerRadius is 10 degrees.

    EllipseShape's default value for \l {QtQuick.Shapes::Shape::preferredRendererType} is
    \c Shape.CurveRenderer.

    \section1 Example Usage

    \snippet ellipseshape.qml ellipseShape

    \image path-ellipseshape.png
*/
QQuickEllipseShape::QQuickEllipseShape(QQuickItem *parent)
    : QQuickShape(*(new QQuickEllipseShapePrivate), parent)
{
    Q_D(QQuickEllipseShape);

    setPreferredRendererType(CurveRenderer);

    setWidth(200);
    setHeight(200);

    d->path = new QQuickShapePath(this);
    d->path->setParent(this);
    d->path->setAsynchronous(true);
    d->path->setStrokeWidth(1);
    d->path->setStrokeColor(QColorConstants::Black);
    d->path->setFillColor(QColorConstants::White);

    d->sp.append(d->path);
    d->path->setParent(this);
    d->extra.value().resourcesList.append(d->path);
}

QQuickEllipseShape::~QQuickEllipseShape() = default;

bool QQuickEllipseShape::hideLine() const
{
    Q_D(const QQuickEllipseShape);
    return d->hideLine;
}

void QQuickEllipseShape::setHideLine(bool hideLine)
{
    Q_D(QQuickEllipseShape);
    if (d->hideLine == hideLine)
        return;
    d->hideLine = hideLine;
    d->updatePath();
    emit hideLineChanged();
}

/*!
    \qmlproperty real QtQuick.Shapes.DesignHelpers::EllipseShape::sweepAngle

    The angular extent in degrees to be drawn from the \l startAngle.

    If set to positive value, the arc is drawn in clockwise direction.
    If set to negative value, the arc is drawn in counter-clockwise direction.

    The default value is \c 360.
*/
qreal QQuickEllipseShape::sweepAngle() const
{
    Q_D(const QQuickEllipseShape);
    return d->sweepAngle;
}

void QQuickEllipseShape::setSweepAngle(qreal sweepAngle)
{
    Q_D(QQuickEllipseShape);
    if (qFuzzyCompare(d->sweepAngle, sweepAngle))
        return;
    d->sweepAngle = sweepAngle;
    d->updatePath();
    emit sweepAngleChanged();
}

/*!
    \qmlproperty real QtQuick.Shapes.DesignHelpers::EllipseShape::startAngle

    The property defines the starting angle in degrees from which to begin
    drawing the ellipse.

    0 degrees points to the top. Angle increases in clockwise direction.

    The default value is \c 0.
*/
qreal QQuickEllipseShape::startAngle() const
{
    Q_D(const QQuickEllipseShape);
    return d->startAngle;
}

void QQuickEllipseShape::setStartAngle(qreal startAngle)
{
    Q_D(QQuickEllipseShape);
    if (qFuzzyCompare(d->startAngle, startAngle))
        return;
    d->startAngle = startAngle;
    d->updatePath();
    emit startAngleChanged();
}

/*!
    \include shapepath.qdocinc {dashOffset-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
*/

qreal QQuickEllipseShape::dashOffset() const
{
    Q_D(const QQuickEllipseShape);
    return d->path->dashOffset();
}

void QQuickEllipseShape::setDashOffset(qreal offset)
{
    Q_D(QQuickEllipseShape);
    if (qFuzzyCompare(d->path->dashOffset(), offset))
        return;
    d->path->setDashOffset(offset);
    d->updatePath();
    emit dashOffsetChanged();
}

/*!
    \qmlproperty real QtQuick.Shapes.DesignHelpers::EllipseShape::cornerRadius

    Controls the rounding of corners where the radial lines meet the elliptical
    arcs. For pie segments, this rounds the connection to the outer arc. For
    donut segments, this also rounds the connections to both inner and outer arcs.

    The default value is \c 10.
*/
qreal QQuickEllipseShape::cornerRadius() const
{
    Q_D(const QQuickEllipseShape);
    return d->cornerRadius;
}

void QQuickEllipseShape::setCornerRadius(qreal cornerRadius)
{
    Q_D(QQuickEllipseShape);
    if (qFuzzyCompare(d->cornerRadius, cornerRadius))
        return;
    d->cornerRadius = cornerRadius;
    d->updatePath();
    emit cornerRadiusChanged();
}

/*!
    \qmlproperty real QtQuick.Shapes.DesignHelpers::EllipseShape::innerArcRatio

    This property defines the ratio between the inner and outer arcs.

    Value range is between 0.0 and 1.0. Setting the value to 0.0 will cause
    the inner arc to collapse toward the center, drawing a solid filled
    ellipse. Setting the value to 1.0 makes the inner arc the same size as the
    outer ellipse, resulting in just an arc. Values between 0.0 and 1.0 create
    hollow elliptical rings.

    The default value is \c 0.
*/
qreal QQuickEllipseShape::innerArcRatio() const
{
    Q_D(const QQuickEllipseShape);
    return d->innerArcRatio;
}

void QQuickEllipseShape::setInnerArcRatio(qreal innerArcRatio)
{
    Q_D(QQuickEllipseShape);
    if (qFuzzyCompare(d->innerArcRatio, innerArcRatio))
        return;
    d->innerArcRatio = innerArcRatio;
    d->updatePath();
    emit innerArcRatioChanged();
}

/*!
    \qmlproperty real QtQuick.Shapes.DesignHelpers::EllipseShape::strokeWidth

    This property holds the stroke width.

    When set to a negative value, no stroking occurs.

    The default value is \c 1.
*/

qreal QQuickEllipseShape::strokeWidth() const
{
    Q_D(const QQuickEllipseShape);
    return d->path->strokeWidth();
}

void QQuickEllipseShape::setStrokeWidth(qreal width)
{
    Q_D(QQuickEllipseShape);
    if (qFuzzyCompare(d->path->strokeWidth(), width))
        return;
    d->path->setStrokeWidth(width);
    d->updatePath();
    emit strokeWidthChanged();
}

/*!
    \qmlproperty color QtQuick.Shapes.DesignHelpers::EllipseShape::fillColor

    This property holds the fill color.

    When set to \c transparent, no filling occurs.

    The default value is \c "white".

    \note If either \l fillGradient is set to something other than \c null, it
    will be used instead of \c fillColor.
*/

QColor QQuickEllipseShape::fillColor() const
{
    Q_D(const QQuickEllipseShape);
    return d->path->fillColor();
}

void QQuickEllipseShape::setFillColor(const QColor &color)
{
    Q_D(QQuickEllipseShape);
    d->path->setFillColor(color);
    d->updatePath();
    emit fillColorChanged();
}

/*!
    \qmlproperty color QtQuick.Shapes.DesignHelpers::EllipseShape::strokeColor

    This property holds the stroking color.

    When set to \c transparent, no stroking occurs.

    The default value is \c "black".
*/

QColor QQuickEllipseShape::strokeColor() const
{
    Q_D(const QQuickEllipseShape);
    return d->path->strokeColor();
}

void QQuickEllipseShape::setStrokeColor(const QColor &color)
{
    Q_D(QQuickEllipseShape);
    d->path->setStrokeColor(color);
    d->updatePath();
    emit strokeColorChanged();
}

/*!
    \include shapepath.qdocinc {capStyle-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
*/

QQuickShapePath::CapStyle QQuickEllipseShape::capStyle() const
{
    Q_D(const QQuickEllipseShape);
    return d->path->capStyle();
}

void QQuickEllipseShape::setCapStyle(QQuickShapePath::CapStyle style)
{
    Q_D(QQuickEllipseShape);
    if (d->path->capStyle() == style)
        return;
    d->path->setCapStyle(style);
    d->updatePath();
    emit capStyleChanged();
}

/*!
    \include shapepath.qdocinc {joinStyle-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
*/

QQuickShapePath::JoinStyle QQuickEllipseShape::joinStyle() const
{
    Q_D(const QQuickEllipseShape);
    return d->path->joinStyle();
}

void QQuickEllipseShape::setJoinStyle(QQuickShapePath::JoinStyle style)
{
    Q_D(QQuickEllipseShape);
    if (d->path->joinStyle() == style)
        return;
    d->path->setJoinStyle(style);
    d->updatePath();
    emit joinStyleChanged();
}

/*!
    \include shapepath.qdocinc {strokeStyle-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
*/

QQuickShapePath::StrokeStyle QQuickEllipseShape::strokeStyle() const
{
    Q_D(const QQuickEllipseShape);
    return d->path->strokeStyle();
}

void QQuickEllipseShape::setStrokeStyle(QQuickShapePath::StrokeStyle style)
{
    Q_D(QQuickEllipseShape);
    if (d->path->strokeStyle() == style)
        return;
    d->path->setStrokeStyle(style);
    d->updatePath();
    emit strokeStyleChanged();
}

/*!
    \include shapepath.qdocinc {fillRule-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
*/

QQuickShapePath::FillRule QQuickEllipseShape::fillRule() const
{
    Q_D(const QQuickEllipseShape);
    return d->path->fillRule();
}

void QQuickEllipseShape::setFillRule(QQuickShapePath::FillRule fillRule)
{
    Q_D(QQuickEllipseShape);
    if (d->path->fillRule() == fillRule)
        return;
    d->path->setFillRule(fillRule);
    d->updatePath();
    emit fillRuleChanged();
}

/*!
    \include shapepath.qdocinc {dashPattern-property} {QtQuick.Shapes.DesignHelpers::EllipseShape}
*/

QVector<qreal> QQuickEllipseShape::dashPattern() const
{
    Q_D(const QQuickEllipseShape);
    return d->path->dashPattern();
}

void QQuickEllipseShape::setDashPattern(const QVector<qreal> &array)
{
    Q_D(QQuickEllipseShape);
    d->path->setDashPattern(array);
    d->updatePath();
    emit dashPatternChanged();
}

/*!
    \qmlproperty ShapeGradient QtQuick.Shapes.DesignHelpers::EllipseShape::fillGradient

    The fillGradient of the ellipse fill color.

    By default, no fillGradient is enabled and the value is null. In this case, the
    fill uses a solid color based on the value of \l fillColor.

    When set, \l fillColor is ignored and filling is done using one of the
    \l ShapeGradient subtypes.

    \note The \l Gradient type cannot be used here. Rather, prefer using one of
    the advanced subtypes, like \l LinearGradient.
*/
QQuickShapeGradient *QQuickEllipseShape::fillGradient() const
{
    Q_D(const QQuickEllipseShape);
    return d->path->fillGradient();
}

void QQuickEllipseShape::setFillGradient(QQuickShapeGradient *fillGradient)
{
    Q_D(QQuickEllipseShape);
    d->path->setFillGradient(fillGradient);
    d->updatePath();
    emit gradientChanged();
}

void QQuickEllipseShape::resetFillGradient()
{
    setFillGradient(nullptr);
}

/*!
    \qmlproperty enumeration QtQuick.Shapes.DesignHelpers::EllipseShape::borderMode

    The \l borderMode property determines where the border is drawn along the
    edge of the ellipse.

    \value EllipseShape.Inside
        The border is drawn along the inside edge of the item and does not
        affect the item width.

        This is the default value.
    \value EllipseShape.Middle
        The border is drawn over the edge of the item and does not
        affect the item width.
    \value EllipseShape.Outside
        The border is drawn along the outside edge of the item and increases
        the item width by the value of \l strokeWidth.

    \sa strokeWidth
*/
QQuickEllipseShape::BorderMode QQuickEllipseShape::borderMode() const
{
    Q_D(const QQuickEllipseShape);
    return d->borderMode;
}

void QQuickEllipseShape::setBorderMode(BorderMode borderMode)
{
    Q_D(QQuickEllipseShape);
    if (borderMode == d->borderMode)
        return;
    d->borderMode = borderMode;
    d->updatePath();
    emit borderModeChanged();
}

void QQuickEllipseShape::resetBorderMode()
{
    setBorderMode(BorderMode::Inside);
}

void QQuickEllipseShape::itemChange(ItemChange change, const ItemChangeData &value)
{
    Q_D(QQuickEllipseShape);

    if (d->path)
        d->updatePath();

    QQuickItem::itemChange(change, value);
}

QT_END_NAMESPACE

#include "moc_qquickellipseshape_p.cpp"
