diff options
Diffstat (limited to 'src/quicktemplates/qquickdial.cpp')
-rw-r--r-- | src/quicktemplates/qquickdial.cpp | 211 |
1 files changed, 191 insertions, 20 deletions
diff --git a/src/quicktemplates/qquickdial.cpp b/src/quicktemplates/qquickdial.cpp index c5e35c39d2..c955e519ca 100644 --- a/src/quicktemplates/qquickdial.cpp +++ b/src/quicktemplates/qquickdial.cpp @@ -57,10 +57,16 @@ QT_BEGIN_NAMESPACE by the user by either touch, mouse, or keys. */ -static const qreal startAngleRadians = (M_PI * 2.0) * (4.0 / 6.0); -static const qreal startAngle = -140; -static const qreal endAngleRadians = (M_PI * 2.0) * (5.0 / 6.0); -static const qreal endAngle = 140; +// The user angle is the clockwise angle between the position and the vertical +// y-axis (12 o clock position). +// Using radians for logic (atan2(...)) and degree for user interface +constexpr qreal toUserAngleDeg(qreal logicAngleRad) { + // minus to turn clockwise, add 90 deg clockwise + return -logicAngleRad / M_PI * 180. + 90; +} + +static const qreal defaultStartAngle = -140; +static const qreal defaultEndAngle = 140; class QQuickDialPrivate : public QQuickControlPrivate { @@ -74,7 +80,7 @@ public: qreal linearPositionAt(const QPointF &point) const; void setPosition(qreal position); void updatePosition(); - bool isLargeChange(const QPointF &eventPos, qreal proposedPosition) const; + bool isLargeChange(qreal proposedPosition) const; bool isHorizontalOrVertical() const; bool handlePress(const QPointF &point, ulong timestamp) override; @@ -91,6 +97,8 @@ public: qreal to = 1; qreal value = 0; qreal position = 0; + qreal startAngle = defaultStartAngle; + qreal endAngle = defaultEndAngle; qreal angle = startAngle; qreal stepSize = 0; QPointF pressPoint; @@ -140,13 +148,34 @@ qreal QQuickDialPrivate::circularPositionAt(const QPointF &point) const { qreal yy = height / 2.0 - point.y(); qreal xx = point.x() - width / 2.0; - qreal angle = (xx || yy) ? std::atan2(yy, xx) : 0; + qreal alpha = (xx || yy) ? toUserAngleDeg(std::atan2(yy, xx)) : 0; + + // Move around the circle to reach the interval. + if (alpha < startAngle && alpha + 360. < endAngle) + alpha += 360.; + else if (alpha >= endAngle && alpha - 360. >= startAngle) + alpha -= 360.; + + // If wrap is on and we are out of the interval [startAngle, endAngle], + // we want to jump to the closest border to make it feel nice and responsive + if ((alpha < startAngle || alpha > endAngle) && wrap) { + if (abs(alpha - startAngle) > abs(endAngle - alpha - 360.)) + alpha += 360.; + else if (abs(alpha - startAngle - 360.) < abs(endAngle - alpha)) + alpha -= 360.; + } - if (angle < M_PI / -2) - angle = angle + M_PI * 2; + // If wrap is off, + // we want to stay as close as possible to the current angle. + // This is important to allow easy setting of boundary values (0,1) + if (!wrap) { + if (abs(angle - alpha) > abs(angle - (alpha + 360.))) + alpha += 360.; + if (abs(angle - alpha) > abs(angle - (alpha - 360.))) + alpha -= 360.; + } - qreal normalizedAngle = (startAngleRadians - angle) / endAngleRadians; - return normalizedAngle; + return (alpha - startAngle) / (endAngle - startAngle); } qreal QQuickDialPrivate::linearPositionAt(const QPointF &point) const @@ -179,12 +208,13 @@ void QQuickDialPrivate::setPosition(qreal pos) { Q_Q(QQuickDial); pos = qBound<qreal>(qreal(0), pos, qreal(1)); - if (qFuzzyCompare(position, pos)) + const qreal alpha = startAngle + pos * qAbs(endAngle - startAngle); + if (qFuzzyCompare(position, pos) && qFuzzyCompare(angle, alpha)) return; + angle = alpha; position = pos; - angle = startAngle + position * qAbs(endAngle - startAngle); emit q->positionChanged(); emit q->angleChanged(); @@ -198,9 +228,11 @@ void QQuickDialPrivate::updatePosition() setPosition(pos); } -bool QQuickDialPrivate::isLargeChange(const QPointF &eventPos, qreal proposedPosition) const +bool QQuickDialPrivate::isLargeChange(qreal proposedPosition) const { - return qAbs(proposedPosition - position) >= qreal(0.5) && eventPos.y() >= height / 2; + if (endAngle - startAngle < 180) + return false; + return qAbs(proposedPosition - position) > qreal(0.5); } bool QQuickDialPrivate::isHorizontalOrVertical() const @@ -223,11 +255,11 @@ bool QQuickDialPrivate::handleMove(const QPointF &point, ulong timestamp) Q_Q(QQuickDial); QQuickControlPrivate::handleMove(point, timestamp); const qreal oldPos = position; - qreal pos = positionAt(point); + qreal pos = qBound(0.0, positionAt(point), 1.0); if (snapMode == QQuickDial::SnapAlways) pos = snapPosition(pos); - if (wrap || isHorizontalOrVertical() || !isLargeChange(point, pos)) { + if (wrap || isHorizontalOrVertical() || !isLargeChange(pos)) { if (live) q->setValue(valueAt(pos)); else @@ -248,7 +280,7 @@ bool QQuickDialPrivate::handleRelease(const QPointF &point, ulong timestamp) if (snapMode != QQuickDial::NoSnap) pos = snapPosition(pos); - if (wrap || isHorizontalOrVertical() || !isLargeChange(point, pos)) + if (wrap || isHorizontalOrVertical() || !isLargeChange(pos)) q->setValue(valueAt(pos)); if (!qFuzzyCompare(pos, oldPos)) emit q->moved(); @@ -420,11 +452,12 @@ qreal QQuickDial::position() const \qmlproperty real QtQuick.Controls::Dial::angle \readonly - This property holds the angle of the handle. + This property holds the clockwise angle of the handle in degrees. - The range is from \c -140 degrees to \c 140 degrees. + The angle is zero at the 12 o'clock position and the range is from + \l startAngle to \c endAngle. - \sa position + \sa position, startAngle, endAngle */ qreal QQuickDial::angle() const { @@ -467,6 +500,129 @@ void QQuickDial::setStepSize(qreal step) emit stepSizeChanged(); } + +/*! + \qmlproperty real QtQuick.Controls::Dial::startAngle + \since 6.6 + + This property holds the starting angle of the dial in degrees. + + This is the \l angle the dial will have for its minimum value, i.e. \l from. + The \l startAngle has to be smaller than the \l endAngle, larger than -360 + and larger or equal to the \l endAngle - 360 degrees. + + \sa endAngle, angle +*/ +qreal QQuickDial::startAngle() const +{ + Q_D(const QQuickDial); + return d->startAngle; +} + +void QQuickDial::setStartAngle(qreal startAngle) +{ + Q_D(QQuickDial); + if (!d->componentComplete) { + // Binding evaluation order can cause warnings with certain combinations + // of start and end angles, so delay the actual setting until after component completion. + // Store the requested value in the existing member to avoid the need for an extra one. + d->startAngle = startAngle; + return; + } + + if (qFuzzyCompare(d->startAngle, startAngle)) + return; + + // do not allow to change direction + if (startAngle >= d->endAngle) { + qmlWarning(this) << "startAngle (" << startAngle + << ") cannot be greater than or equal to endAngle (" << d->endAngle << ")"; + return; + } + + // Keep the interval around 0 + if (startAngle <= -360.) { + qmlWarning(this) << "startAngle (" << startAngle << ") cannot be less than or equal to -360"; + return; + } + + // keep the interval [startAngle, endAngle] unique + if (startAngle < d->endAngle - 360.) { + qmlWarning(this) << "Difference between startAngle (" << startAngle + << ") and endAngle (" << d->endAngle << ") cannot be greater than 360." + << " Changing endAngle to avoid overlaps."; + d->endAngle = startAngle + 360.; + emit endAngleChanged(); + } + + d->startAngle = startAngle; + // changing the startAngle will change the angle + // if the value is kept constant + d->updatePosition(); + emit startAngleChanged(); +} + +/*! + \qmlproperty real QtQuick.Controls::Dial::endAngle + \since 6.6 + + This property holds the end angle of the dial in degrees. + + This is the \l angle the dial will have for its maximum value, i.e. \l to. + The \l endAngle has to be bigger than the \l startAngle, smaller than 720 + and smaller or equal than the \l startAngle + 360 degrees. + + \sa endAngle, angle +*/ +qreal QQuickDial::endAngle() const +{ + Q_D(const QQuickDial); + return d->endAngle; + +} + +void QQuickDial::setEndAngle(qreal endAngle) +{ + Q_D(QQuickDial); + if (!d->componentComplete) { + // Binding evaluation order can cause warnings with certain combinations + // of start and end angles, so delay the actual setting until after component completion. + // Store the requested value in the existing member to avoid the need for an extra one. + d->endAngle = endAngle; + return; + } + + if (qFuzzyCompare(d->endAngle, endAngle)) + return; + + if (endAngle <= d->startAngle) { + qmlWarning(this) << "endAngle (" << endAngle + << ") cannot be less than or equal to startAngle (" << d->startAngle << ")"; + return; + } + + // Keep the interval around 0 + if (endAngle >= 720.) { + qmlWarning(this) << "endAngle (" << endAngle << ") cannot be greater than or equal to 720"; + return; + } + + // keep the interval [startAngle, endAngle] unique + if (endAngle > d->startAngle + 360.) { + qmlWarning(this) << "Difference between startAngle (" << d->startAngle + << ") and endAngle (" << endAngle << ") cannot be greater than 360." + << " Changing startAngle to avoid overlaps."; + d->startAngle = endAngle - 360.; + emit startAngleChanged(); + } + + d->endAngle = endAngle; + // changing the startAngle will change the angle + // if the value is kept constant + d->updatePosition(); + emit endAngleChanged(); +} + /*! \qmlproperty enumeration QtQuick.Controls::Dial::snapMode @@ -806,6 +962,21 @@ void QQuickDial::componentComplete() Q_D(QQuickDial); d->executeHandle(true); QQuickControl::componentComplete(); + + // Set the (delayed) start and end angles, if necessary (see the setters for more info). + if (!qFuzzyCompare(d->startAngle, defaultStartAngle)) { + const qreal startAngle = d->startAngle; + // Temporarily set it to something else so that it sees that it has changed. + d->startAngle = defaultStartAngle; + setStartAngle(startAngle); + } + + if (!qFuzzyCompare(d->endAngle, defaultEndAngle)) { + const qreal endAngle = d->endAngle; + d->endAngle = defaultEndAngle; + setEndAngle(endAngle); + } + setValue(d->value); d->updatePosition(); } |