1 | // Copyright (C) 2016 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 | |
4 | #include "qquicktransitionmanager_p_p.h" |
5 | |
6 | #include "qquicktransition_p.h" |
7 | #include "qquickstate_p_p.h" |
8 | |
9 | #include <private/qqmlbinding_p.h> |
10 | #include <private/qqmlglobal_p.h> |
11 | #include <private/qqmlproperty_p.h> |
12 | |
13 | #include <QtCore/qdebug.h> |
14 | #include <private/qanimationjobutil_p.h> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | Q_DECLARE_LOGGING_CATEGORY(lcStates) |
19 | |
20 | class QQuickTransitionManagerPrivate |
21 | { |
22 | public: |
23 | QQuickTransitionManagerPrivate() |
24 | : state(nullptr), transitionInstance(nullptr) {} |
25 | |
26 | void applyBindings(); |
27 | typedef QList<QQuickSimpleAction> SimpleActionList; |
28 | QQuickState *state; |
29 | QQuickTransitionInstance *transitionInstance; |
30 | QQuickStateOperation::ActionList bindingsList; |
31 | SimpleActionList completeList; |
32 | }; |
33 | |
34 | QQuickTransitionManager::QQuickTransitionManager() |
35 | : d(new QQuickTransitionManagerPrivate) |
36 | { |
37 | } |
38 | |
39 | void QQuickTransitionManager::setState(QQuickState *s) |
40 | { |
41 | d->state = s; |
42 | } |
43 | |
44 | QQuickTransitionManager::~QQuickTransitionManager() |
45 | { |
46 | delete d->transitionInstance; |
47 | d->transitionInstance = nullptr; |
48 | delete d; d = nullptr; |
49 | } |
50 | |
51 | bool QQuickTransitionManager::isRunning() const |
52 | { |
53 | return d->transitionInstance && d->transitionInstance->isRunning(); |
54 | } |
55 | |
56 | void QQuickTransitionManager::complete() |
57 | { |
58 | d->applyBindings(); |
59 | |
60 | // Explicitly take a copy in case the write action triggers a script that modifies the list. |
61 | QQuickTransitionManagerPrivate::SimpleActionList completeListCopy = d->completeList; |
62 | for (const QQuickSimpleAction &action : std::as_const(t&: completeListCopy)) |
63 | action.property().write(action.value()); |
64 | |
65 | d->completeList.clear(); |
66 | |
67 | if (d->state) |
68 | static_cast<QQuickStatePrivate*>(QObjectPrivate::get(o: d->state))->complete(); |
69 | |
70 | finished(); |
71 | } |
72 | |
73 | void QQuickTransitionManagerPrivate::applyBindings() |
74 | { |
75 | for (const QQuickStateAction &action : std::as_const(t&: bindingsList)) { |
76 | if (auto binding = action.toBinding; binding) { |
77 | binding.installOn(target: action.property, mode: QQmlAnyBinding::RespectInterceptors); |
78 | } else if (action.event) { |
79 | if (action.reverseEvent) |
80 | action.event->reverse(); |
81 | else |
82 | action.event->execute(); |
83 | } |
84 | |
85 | } |
86 | |
87 | bindingsList.clear(); |
88 | } |
89 | |
90 | void QQuickTransitionManager::finished() |
91 | { |
92 | } |
93 | |
94 | void QQuickTransitionManager::transition(const QList<QQuickStateAction> &list, |
95 | QQuickTransition *transition, |
96 | QObject *defaultTarget) |
97 | { |
98 | RETURN_IF_DELETED(cancel()); |
99 | |
100 | // The copy below is ON PURPOSE, because firing actions might involve scripts that modify the list. |
101 | QQuickStateOperation::ActionList applyList = list; |
102 | |
103 | // Determine which actions are binding changes and disable any current bindings |
104 | for (const QQuickStateAction &action : std::as_const(t&: applyList)) { |
105 | if (action.toBinding) |
106 | d->bindingsList << action; |
107 | if (action.fromBinding) { |
108 | auto property = action.property; |
109 | QQmlAnyBinding::removeBindingFrom(prop&: property); // Disable current binding |
110 | } |
111 | if (action.event && action.event->changesBindings()) { //### assume isReversable()? |
112 | d->bindingsList << action; |
113 | action.event->clearBindings(); |
114 | } |
115 | } |
116 | |
117 | // Animated transitions need both the start and the end value for |
118 | // each property change. In the presence of bindings, the end values |
119 | // are non-trivial to calculate. As a "best effort" attempt, we first |
120 | // apply all the property and binding changes, then read all the actual |
121 | // final values, then roll back the changes and proceed as normal. |
122 | // |
123 | // This doesn't catch everything, and it might be a little fragile in |
124 | // some cases - but whatcha going to do? |
125 | if (transition && !d->bindingsList.isEmpty()) { |
126 | |
127 | // Apply all the property and binding changes |
128 | for (const QQuickStateAction &action : std::as_const(t&: applyList)) { |
129 | if (auto binding = action.toBinding; binding) { |
130 | binding.installOn(target: action.property); |
131 | } else if (!action.event) { |
132 | QQmlPropertyPrivate::write(that: action.property, action.toValue, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); |
133 | } else if (action.event->isReversable()) { |
134 | if (action.reverseEvent) |
135 | action.event->reverse(); |
136 | else |
137 | action.event->execute(); |
138 | } |
139 | } |
140 | |
141 | // Read all the end values for binding changes. |
142 | for (auto it = applyList.begin(), eit = applyList.end(); it != eit; ++it) { |
143 | if (it->event) { |
144 | it->event->saveTargetValues(); |
145 | continue; |
146 | } |
147 | const QQmlProperty &prop = it->property; |
148 | if (it->toBinding || !it->toValue.isValid()) |
149 | it->toValue = prop.read(); |
150 | } |
151 | |
152 | // Revert back to the original values |
153 | for (const QQuickStateAction &action : std::as_const(t&: applyList)) { |
154 | if (action.event) { |
155 | if (action.event->isReversable()) { |
156 | action.event->clearBindings(); |
157 | action.event->rewind(); |
158 | action.event->clearBindings(); //### shouldn't be needed |
159 | } |
160 | continue; |
161 | } |
162 | |
163 | if (action.toBinding) { |
164 | auto property = action.property; |
165 | QQmlAnyBinding::removeBindingFrom(prop&: property); // Make sure this is disabled during the transition |
166 | } |
167 | |
168 | QQmlPropertyPrivate::write(that: action.property, action.fromValue, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); |
169 | } |
170 | } |
171 | |
172 | if (transition) { |
173 | QList<QQmlProperty> touched; |
174 | QQuickTransitionInstance *oldInstance = d->transitionInstance; |
175 | d->transitionInstance = transition->prepare(actions&: applyList, after&: touched, end: this, defaultTarget); |
176 | d->transitionInstance->start(); |
177 | if (oldInstance && oldInstance != d->transitionInstance) |
178 | delete oldInstance; |
179 | |
180 | // Modify the action list to remove actions handled in the transition |
181 | auto isHandledInTransition = [this, touched](const QQuickStateAction &action) { |
182 | if (action.event) { |
183 | return action.actionDone; |
184 | } else { |
185 | if (touched.contains(t: action.property)) { |
186 | if (action.toValue != action.fromValue) |
187 | d->completeList << QQuickSimpleAction(action, QQuickSimpleAction::EndState); |
188 | return true; |
189 | } |
190 | } |
191 | return false; |
192 | }; |
193 | auto newEnd = std::remove_if(first: applyList.begin(), last: applyList.end(), pred: isHandledInTransition); |
194 | applyList.erase(abegin: newEnd, aend: applyList.end()); |
195 | } |
196 | |
197 | // Any actions remaining have not been handled by the transition and should |
198 | // be applied immediately. We skip applying bindings, as they are all |
199 | // applied at the end in applyBindings() to avoid any nastiness mid |
200 | // transition |
201 | for (const QQuickStateAction &action : std::as_const(t&: applyList)) { |
202 | if (action.event && !action.event->changesBindings()) { |
203 | if (action.event->isReversable() && action.reverseEvent) |
204 | action.event->reverse(); |
205 | else |
206 | action.event->execute(); |
207 | } else if (!action.event && !action.toBinding) { |
208 | action.property.write(action.toValue); |
209 | } |
210 | } |
211 | if (lcStates().isDebugEnabled()) { |
212 | for (const QQuickStateAction &action : std::as_const(t&: applyList)) { |
213 | if (action.event) |
214 | qCDebug(lcStates) << "no transition for event:" << action.event->type(); |
215 | else |
216 | qCDebug(lcStates) << "no transition for:" << action.property.object() |
217 | << action.property.name() << "from:" << action.fromValue |
218 | << "to:" << action.toValue; |
219 | } |
220 | } |
221 | |
222 | if (!transition) |
223 | complete(); |
224 | } |
225 | |
226 | void QQuickTransitionManager::cancel() |
227 | { |
228 | if (d->transitionInstance && d->transitionInstance->isRunning()) |
229 | RETURN_IF_DELETED(d->transitionInstance->stop()); |
230 | |
231 | for (const QQuickStateAction &action : std::as_const(t&: d->bindingsList)) { |
232 | if (action.toBinding && action.deletableToBinding) { |
233 | auto property = action.property; |
234 | QQmlAnyBinding::removeBindingFrom(prop&: property); |
235 | } else if (action.event) { |
236 | //### what do we do here? |
237 | } |
238 | |
239 | } |
240 | d->bindingsList.clear(); |
241 | d->completeList.clear(); |
242 | } |
243 | |
244 | QT_END_NAMESPACE |
245 | |