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
qquickboundaryrule.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 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
6
7#include <qqmlcontext.h>
8#include <qqmlinfo.h>
9#include <private/qqmlproperty_p.h>
10#include <private/qqmlengine_p.h>
11#include <private/qobject_p.h>
12#include <private/qquickanimation_p_p.h>
13#include <QtCore/qloggingcategory.h>
14
16
17Q_STATIC_LOGGING_CATEGORY(lcBR, "qt.quick.boundaryrule")
18
19class QQuickBoundaryReturnJob;
21{
22 Q_DECLARE_PUBLIC(QQuickBoundaryRule)
23public:
25
29 // read-only properties, updated on each write()
30 qreal targetValue = 0; // after easing was applied
33 // settable properties
39 int returnDuration = 100;
41 bool enabled = true;
42 bool completed = false;
43
44 qreal easedOvershoot(qreal overshootingValue);
47};
48
50{
51public:
57
59
61
62 void updateState(QAbstractAnimationJob::State newState,
63 QAbstractAnimationJob::State oldState) override;
64
66 qreal fromValue; // snapshot of initial value from which we're returning
67 qreal toValue; // target property value to which we're returning
68};
69
71{
72 // The easing property tells how to behave when the property is being
73 // externally manipulated beyond the bounds. During returnToBounds()
74 // we run it in reverse, by reversing time.
75 qreal progress = (duration() - t) / qreal(duration());
76 qreal easingValue = boundaryRule->easing.valueForProgress(progress);
77 qreal delta = qAbs(fromValue - toValue) * easingValue;
78 qreal value = (fromValue > toValue ? toValue + delta : toValue - delta);
79 qCDebug(lcBR) << t << "ms" << qRound(progress * 100) << "% easing" << easingValue << "->" << value;
80 QQmlPropertyPrivate::write(boundaryRule->property, value,
81 QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
82}
83
84void QQuickBoundaryReturnJob::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState)
85{
86 Q_UNUSED(oldState);
87 if (newState == QAbstractAnimationJob::Stopped) {
88 qCDebug(lcBR) << "return animation done";
91 }
92}
93
94/*!
95 \qmltype BoundaryRule
96//! \nativetype QQuickBoundaryRule
97 \inqmlmodule Qt.labs.animation
98 \ingroup qtquick-transitions-animations
99 \ingroup qtquick-interceptors
100 \brief Defines a restriction on the range of values that can be set on a numeric property.
101 \since 5.14
102
103 A BoundaryRule defines the range of values that a particular property is
104 allowed to have. When an out-of-range value would otherwise be set,
105 it applies "resistance" via an easing curve.
106
107 For example, the following BoundaryRule prevents DragHandler from dragging
108 the Rectangle too far:
109
110 \snippet qml/boundaryRule.qml 0
111
112 Note that a property cannot have more than one assigned BoundaryRule.
113
114 \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation#Behaviors}{Behavior
115example}, {Qt Qml}, {Qt Quick Examples - Pointer Handlers}
116*/
117
118QQuickBoundaryRule::QQuickBoundaryRule(QObject *parent)
119 : QObject(*(new QQuickBoundaryRulePrivate), parent)
120 , QQmlPropertyValueInterceptor()
121{
122}
123
124QQuickBoundaryRule::~QQuickBoundaryRule()
125{
126 Q_D(QQuickBoundaryRule);
127 // stop any running animation and
128 // prevent QQuickBoundaryReturnJob::updateState() from accessing QQuickBoundaryRulePrivate
129 delete d->returnAnimationJob;
130}
131
132/*!
133 \qmlproperty bool Qt.labs.animation::BoundaryRule::enabled
134
135 This property holds whether the rule will be enforced when the tracked
136 property changes value.
137
138 By default a BoundaryRule is enabled.
139*/
140bool QQuickBoundaryRule::enabled() const
141{
142 Q_D(const QQuickBoundaryRule);
143 return d->enabled;
144}
145
146void QQuickBoundaryRule::setEnabled(bool enabled)
147{
148 Q_D(QQuickBoundaryRule);
149 if (d->enabled == enabled)
150 return;
151 d->enabled = enabled;
152 emit enabledChanged();
153}
154
155/*!
156 \qmlproperty real Qt.labs.animation::BoundaryRule::minimum
157
158 This property holds the smallest unconstrained value that the property is
159 allowed to have. If the property is set to a smaller value, it will be
160 constrained by \l easing and \l minimumOvershoot.
161
162 The default is \c 0.
163*/
164qreal QQuickBoundaryRule::minimum() const
165{
166 Q_D(const QQuickBoundaryRule);
167 return d->minimum;
168}
169
170void QQuickBoundaryRule::setMinimum(qreal minimum)
171{
172 Q_D(QQuickBoundaryRule);
173 if (qFuzzyCompare(d->minimum, minimum))
174 return;
175 d->minimum = minimum;
176 emit minimumChanged();
177}
178
179/*!
180 \qmlproperty real Qt.labs.animation::BoundaryRule::minimumOvershoot
181
182 This property holds the amount that the property is allowed to be
183 less than \l minimum. Whenever the value is less than \l minimum
184 and greater than \c {minimum - minimumOvershoot}, it is constrained
185 by the \l easing curve. When the value attempts to go under
186 \c {minimum - minimumOvershoots} there is a hard stop.
187
188 The default is \c 0.
189*/
190qreal QQuickBoundaryRule::minimumOvershoot() const
191{
192 Q_D(const QQuickBoundaryRule);
193 return d->minimumOvershoot;
194}
195
196void QQuickBoundaryRule::setMinimumOvershoot(qreal minimumOvershoot)
197{
198 Q_D(QQuickBoundaryRule);
199 if (qFuzzyCompare(d->minimumOvershoot, minimumOvershoot))
200 return;
201 d->minimumOvershoot = minimumOvershoot;
202 emit minimumOvershootChanged();
203}
204
205/*!
206 \qmlproperty real Qt.labs.animation::BoundaryRule::maximum
207
208 This property holds the largest unconstrained value that the property is
209 allowed to have. If the property is set to a larger value, it will be
210 constrained by \l easing and \l maximumOvershoot.
211
212 The default is \c 1.
213*/
214qreal QQuickBoundaryRule::maximum() const
215{
216 Q_D(const QQuickBoundaryRule);
217 return d->maximum;
218}
219
220void QQuickBoundaryRule::setMaximum(qreal maximum)
221{
222 Q_D(QQuickBoundaryRule);
223 if (qFuzzyCompare(d->maximum, maximum))
224 return;
225 d->maximum = maximum;
226 emit maximumChanged();
227}
228
229/*!
230 \qmlproperty real Qt.labs.animation::BoundaryRule::maximumOvershoot
231
232 This property holds the amount that the property is allowed to be
233 more than \l maximum. Whenever the value is greater than \l maximum
234 and less than \c {maximum + maximumOvershoot}, it is constrained
235 by the \l easing curve. When the value attempts to exceed
236 \c {maximum + maximumOvershoot} there is a hard stop.
237
238 The default is 0.
239*/
240qreal QQuickBoundaryRule::maximumOvershoot() const
241{
242 Q_D(const QQuickBoundaryRule);
243 return d->maximumOvershoot;
244}
245
246void QQuickBoundaryRule::setMaximumOvershoot(qreal maximumOvershoot)
247{
248 Q_D(QQuickBoundaryRule);
249 if (qFuzzyCompare(d->maximumOvershoot, maximumOvershoot))
250 return;
251 d->maximumOvershoot = maximumOvershoot;
252 emit maximumOvershootChanged();
253}
254
255/*!
256 \qmlproperty real Qt.labs.animation::BoundaryRule::overshootScale
257
258 This property holds the amount by which the \l easing is scaled during the
259 overshoot condition. For example if an Item is restricted from moving more
260 than 100 pixels beyond some limit, and the user (by means of some Input
261 Handler) is trying to drag it 100 pixels past the limit, if overshootScale
262 is set to 1, the user will succeed: the only effect of the easing curve is
263 to change the rate at which the item moves from overshoot 0 to overshoot
264 100. But if it is set to 0.5, the BoundaryRule provides resistance such
265 that when the user tries to move 100 pixels, the Item will only move 50
266 pixels; and the easing curve modulates the rate of movement such that it
267 may move in sync with the user's attempted movement at the beginning, and
268 then slows down, depending on the shape of the easing curve.
269
270 The default is 0.5.
271*/
272qreal QQuickBoundaryRule::overshootScale() const
273{
274 Q_D(const QQuickBoundaryRule);
275 return d->overshootScale;
276}
277
278void QQuickBoundaryRule::setOvershootScale(qreal overshootScale)
279{
280 Q_D(QQuickBoundaryRule);
281 if (qFuzzyCompare(d->overshootScale, overshootScale))
282 return;
283 d->overshootScale = overshootScale;
284 emit overshootScaleChanged();
285}
286
287/*!
288 \qmlproperty real Qt.labs.animation::BoundaryRule::currentOvershoot
289
290 This property holds the amount by which the most recently set value of the
291 intercepted property exceeds \l maximum or is less than \l minimum.
292
293 It is positive if the property value exceeds \l maximum, negative if the
294 property value is less than \l minimum, or 0 if the property value is
295 within both boundaries.
296*/
297qreal QQuickBoundaryRule::currentOvershoot() const
298{
299 Q_D(const QQuickBoundaryRule);
300 return d->currentOvershoot;
301}
302
303/*!
304 \qmlproperty real Qt.labs.animation::BoundaryRule::peakOvershoot
305
306 This property holds the most-positive or most-negative value of
307 \l currentOvershoot that has been seen, until \l returnToBounds() is called.
308
309 This can be useful when the intercepted property value is known to
310 fluctuate, and you want to find and react to the maximum amount of
311 overshoot rather than to the fluctuations.
312
313 \sa overshootFilter
314*/
315qreal QQuickBoundaryRule::peakOvershoot() const
316{
317 Q_D(const QQuickBoundaryRule);
318 return d->peakOvershoot;
319}
320
321/*!
322 \qmlproperty enumeration Qt.labs.animation::BoundaryRule::overshootFilter
323
324 This property specifies the aggregation function that will be applied to
325 the intercepted property value.
326
327 If this is set to \c BoundaryRule.None (the default), the intercepted
328 property will hold a value whose overshoot is limited to \l currentOvershoot.
329 If this is set to \c BoundaryRule.Peak, the intercepted property will hold
330 a value whose overshoot is limited to \l peakOvershoot.
331*/
332QQuickBoundaryRule::OvershootFilter QQuickBoundaryRule::overshootFilter() const
333{
334 Q_D(const QQuickBoundaryRule);
335 return d->overshootFilter;
336}
337
338void QQuickBoundaryRule::setOvershootFilter(OvershootFilter overshootFilter)
339{
340 Q_D(QQuickBoundaryRule);
341 if (d->overshootFilter == overshootFilter)
342 return;
343 d->overshootFilter = overshootFilter;
344 emit overshootFilterChanged();
345}
346
347/*!
348 \qmlmethod bool Qt.labs.animation::BoundaryRule::returnToBounds
349
350 Returns the intercepted property to a value between \l minimum and
351 \l maximum, such that \l currentOvershoot and \l peakOvershoot are both
352 zero. This will be animated if \l returnDuration is greater than zero.
353
354 Returns true if the value needed to be adjusted, or false if it was already
355 within bounds.
356
357 \sa returnedToBounds
358*/
359bool QQuickBoundaryRule::returnToBounds()
360{
361 Q_D(QQuickBoundaryRule);
362 if (d->returnAnimationJob) {
363 qCDebug(lcBR) << "animation already in progress";
364 return true;
365 }
366 if (currentOvershoot() > 0) {
367 if (d->returnDuration > 0)
368 d->returnAnimationJob = new QQuickBoundaryReturnJob(d, maximum());
369 else
370 write(maximum());
371 } else if (currentOvershoot() < 0) {
372 if (d->returnDuration > 0)
373 d->returnAnimationJob = new QQuickBoundaryReturnJob(d, minimum());
374 else
375 write(minimum());
376 } else {
377 return false;
378 }
379 if (d->returnAnimationJob) {
380 qCDebug(lcBR) << d->property.name() << "on" << d->property.object()
381 << ": animating from" << d->returnAnimationJob->fromValue << "to" << d->returnAnimationJob->toValue;
382 d->returnAnimationJob->start();
383 } else {
384 d->resetOvershoot();
385 qCDebug(lcBR) << d->property.name() << "on" << d->property.object() << ": returned to" << d->property.read();
386 emit returnedToBounds();
387 }
388 return true;
389}
390
391/*!
392 \qmlsignal Qt.labs.animation::BoundaryRule::returnedToBounds()
393
394 This signal is emitted when \l currentOvershoot returns to \c 0 again,
395 after the \l maximum or \l minimum constraint has been violated.
396 If the return is animated, the signal is emitted when the animation
397 completes.
398
399 \sa returnDuration, returnToBounds()
400*/
401
402/*!
403 \qmlproperty enumeration Qt.labs.animation::BoundaryRule::easing
404
405 This property holds the easing curve to be applied in overshoot mode
406 (whenever the \l minimum or \l maximum constraint is violated, while
407 the value is still within the respective overshoot range).
408
409 The default easing curve is \l QEasingCurve::OutQuad.
410*/
411QEasingCurve QQuickBoundaryRule::easing() const
412{
413 Q_D(const QQuickBoundaryRule);
414 return d->easing;
415}
416
417void QQuickBoundaryRule::setEasing(const QEasingCurve &easing)
418{
419 Q_D(QQuickBoundaryRule);
420 if (d->easing == easing)
421 return;
422 d->easing = easing;
423 emit easingChanged();
424}
425
426/*!
427 \qmlproperty int Qt.labs.animation::BoundaryRule::returnDuration
428
429 This property holds the amount of time in milliseconds that
430 \l returnToBounds() will take to return the target property to the nearest bound.
431 If it is set to 0, returnToBounds() will set the property immediately
432 rather than creating an animation job.
433
434 The default is 100 ms.
435*/
436int QQuickBoundaryRule::returnDuration() const
437{
438 Q_D(const QQuickBoundaryRule);
439 return d->returnDuration;
440}
441
442void QQuickBoundaryRule::setReturnDuration(int duration)
443{
444 Q_D(QQuickBoundaryRule);
445 if (d->returnDuration == duration)
446 return;
447 d->returnDuration = duration;
448 emit returnDurationChanged();
449}
450
451void QQuickBoundaryRule::classBegin()
452{
453
454}
455
456void QQuickBoundaryRule::componentComplete()
457{
458 Q_D(QQuickBoundaryRule);
459 d->completed = true;
460}
461
462void QQuickBoundaryRule::write(const QVariant &value)
463{
464 bool conversionOk = false;
465 qreal rValue = value.toReal(&conversionOk);
466 if (!conversionOk) {
467 qWarning() << "BoundaryRule doesn't work with non-numeric values:" << value;
468 return;
469 }
470 Q_D(QQuickBoundaryRule);
471 bool bypass = !d->enabled || !d->completed || QQmlEnginePrivate::designerMode();
472 if (bypass) {
473 QQmlPropertyPrivate::write(d->property, value,
474 QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
475 return;
476 }
477
478 d->targetValue = d->easedOvershoot(rValue);
479 QQmlPropertyPrivate::write(d->property, d->targetValue,
480 QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
481}
482
483void QQuickBoundaryRule::setTarget(const QQmlProperty &property)
484{
485 Q_D(QQuickBoundaryRule);
486 d->property = property;
487}
488
489/*!
490 \internal
491 Given that something is trying to set the target property to \a value,
492 this function applies the easing curve and returns the value that the
493 property should actually get instead.
494*/
496{
497 qreal ret = value;
498 Q_Q(QQuickBoundaryRule);
499 if (value > maximum) {
500 qreal overshootWas = currentOvershoot;
501 currentOvershoot = value - maximum;
502 if (!qFuzzyCompare(overshootWas, currentOvershoot))
503 emit q->currentOvershootChanged();
504 overshootWas = peakOvershoot;
505 peakOvershoot = qMax(currentOvershoot, peakOvershoot);
506 if (!qFuzzyCompare(overshootWas, peakOvershoot))
507 emit q->peakOvershootChanged();
508 ret = maximum + maximumOvershoot * easing.valueForProgress(
509 (overshootFilter == QQuickBoundaryRule::OvershootFilter::Peak ? peakOvershoot : currentOvershoot)
510 * overshootScale / maximumOvershoot);
511 qCDebug(lcBR).nospace() << value << " overshoots maximum " << maximum << " by "
512 << currentOvershoot << " (peak " << peakOvershoot << "): eased to " << ret;
513 } else if (value < minimum) {
514 qreal overshootWas = currentOvershoot;
515 currentOvershoot = value - minimum;
516 if (!qFuzzyCompare(overshootWas, currentOvershoot))
517 emit q->currentOvershootChanged();
518 overshootWas = peakOvershoot;
519 peakOvershoot = qMin(currentOvershoot, peakOvershoot);
520 if (!qFuzzyCompare(overshootWas, peakOvershoot))
521 emit q->peakOvershootChanged();
522 ret = minimum - minimumOvershoot * easing.valueForProgress(
523 -(overshootFilter == QQuickBoundaryRule::OvershootFilter::Peak ? peakOvershoot : currentOvershoot)
524 * overshootScale / minimumOvershoot);
525 qCDebug(lcBR).nospace() << value << " overshoots minimum " << minimum << " by "
526 << currentOvershoot << " (peak " << peakOvershoot << "): eased to " << ret;
527 } else {
529 }
530 return ret;
531}
532
533/*!
534 \internal
535 Resets the currentOvershoot and peakOvershoot
536 properties to zero.
537*/
539{
540 Q_Q(QQuickBoundaryRule);
541 if (!qFuzzyCompare(peakOvershoot, 0)) {
542 peakOvershoot = 0;
543 emit q->peakOvershootChanged();
544 }
545 if (!qFuzzyCompare(currentOvershoot, 0)) {
546 currentOvershoot = 0;
547 emit q->currentOvershootChanged();
548 }
549}
550
552{
553 Q_Q(QQuickBoundaryRule);
554 delete returnAnimationJob;
555 returnAnimationJob = nullptr;
556 emit q->returnedToBounds();
557}
558
559QT_END_NAMESPACE
560
561#include "moc_qquickboundaryrule_p.cpp"
void updateCurrentTime(int) override
int duration() const override
QQuickBoundaryReturnJob(QQuickBoundaryRulePrivate *br, qreal to)
void updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState) override
QQuickBoundaryRulePrivate * boundaryRule
qreal easedOvershoot(qreal overshootingValue)
QQuickBoundaryReturnJob * returnAnimationJob