1use crate::{
2 ActivePropertyTransition, AnimationHandle, AnimationRuntime, AnimationTargetId, BehaviorRule,
3 Duration, PropertyTransitionProgress, PropertyTransitionRegistration, Timing,
4 behavior::TransitionValueKind, property::PropertySpec, runtime::AnimationClock,
5};
6
7#[derive(Debug, Clone, PartialEq)]
39pub struct PropertyTransition<K: TransitionValueKind>
40where
41 K::Inner: Copy + PartialEq,
42{
43 target: AnimationTargetId,
44 property: PropertySpec<K>,
45 timing: Timing,
46 current: Option<K::Inner>,
47 active: Option<ActivePropertyTransition<K>>,
48}
49
50impl<K> PropertyTransition<K>
52where
53 K: TransitionValueKind,
54 K::Inner: Copy + PartialEq,
55{
56 #[must_use]
58 pub fn new(target: AnimationTargetId, property: PropertySpec<K>) -> Self {
59 Self::from_rule(target, &BehaviorRule::new(property))
60 }
61
62 #[must_use]
64 pub const fn from_rule(target: AnimationTargetId, rule: &BehaviorRule<K>) -> Self {
65 Self {
66 target,
67 property: rule.property(),
68 timing: rule.timing(),
69 current: None,
70 active: None,
71 }
72 }
73
74 #[must_use]
76 pub const fn with_timing(mut self, timing: Timing) -> Self {
77 self.timing = timing;
78 self
79 }
80
81 #[must_use]
83 pub const fn target(&self) -> AnimationTargetId {
84 self.target
85 }
86
87 #[must_use]
89 pub const fn property(&self) -> PropertySpec<K> {
90 self.property
91 }
92
93 #[must_use]
95 pub const fn timing(&self) -> Timing {
96 self.timing
97 }
98
99 #[must_use]
101 pub const fn current_value(&self) -> Option<K::Inner> {
102 self.current
103 }
104
105 #[must_use]
107 pub const fn active_handle(&self) -> Option<AnimationHandle> {
108 match &self.active {
109 Some(active) => Some(active.handle()),
110 None => None,
111 }
112 }
113
114 #[must_use]
116 pub fn is_active<C: AnimationClock>(&self, runtime: &AnimationRuntime<C>) -> bool {
117 match &self.active {
118 Some(active) => runtime.contains(self.target, active.handle()),
119 None => false,
120 }
121 }
122
123 pub fn handle_completion<C: AnimationClock>(&mut self, runtime: &AnimationRuntime<C>) -> bool {
127 let Some(active) = &self.active else {
128 return false;
129 };
130
131 if runtime.contains(self.target, active.handle()) {
132 return false;
133 }
134
135 self.active = None;
136 true
137 }
138
139 #[must_use]
141 pub const fn active_transition(&self) -> Option<&ActivePropertyTransition<K>> {
142 self.active.as_ref()
143 }
144
145 #[must_use]
147 pub fn active_progress_at(&self, timestamp: Duration) -> Option<PropertyTransitionProgress<K>> {
148 self.active
149 .as_ref()
150 .map(|active| active.progress_at(timestamp))
151 }
152
153 pub fn transition_to<C: AnimationClock>(
159 &mut self,
160 runtime: &mut AnimationRuntime<C>,
161 value: K::Inner,
162 ) -> Option<PropertyTransitionRegistration> {
163 self.invalidate_if_stale(runtime);
164
165 let Some(previous) = self.current else {
166 self.current = Some(value);
167 return None;
168 };
169
170 if previous == value {
171 return None;
172 }
173
174 let from = self.current_visual_value(runtime).unwrap_or(previous);
175
176 Some(self.register_from(runtime, from, value))
177 }
178
179 pub fn transition_from_visual<C: AnimationClock>(
186 &mut self,
187 runtime: &mut AnimationRuntime<C>,
188 visual: K::Inner,
189 value: K::Inner,
190 ) -> Option<PropertyTransitionRegistration> {
191 self.invalidate_if_stale(runtime);
192
193 if self.current == Some(value) {
194 return None;
195 }
196
197 self.current = Some(value);
198
199 if visual == value {
200 return None;
201 }
202
203 Some(self.register_from(runtime, visual, value))
204 }
205
206 pub fn retarget_to<C: AnimationClock>(
213 &mut self,
214 runtime: &mut AnimationRuntime<C>,
215 value: K::Inner,
216 ) -> Option<PropertyTransitionRegistration> {
217 self.invalidate_if_stale(runtime);
218
219 if self.current == Some(value) {
220 return None;
221 }
222
223 let from = self.current_visual_value(runtime)?;
224
225 Some(self.register_from(runtime, from, value))
226 }
227
228 pub fn interrupt_from_visual<C: AnimationClock>(
236 &mut self,
237 runtime: &mut AnimationRuntime<C>,
238 visual: K::Inner,
239 value: K::Inner,
240 ) -> Option<PropertyTransitionRegistration> {
241 self.invalidate_if_stale(runtime);
242
243 if self.active.is_none() && self.current == Some(value) {
244 return None;
245 }
246
247 self.current = Some(value);
248
249 if visual == value {
250 if let Some(active) = self.active.take() {
251 runtime.cancel(self.target, active.handle());
252 }
253
254 return None;
255 }
256
257 Some(self.register_from(runtime, visual, value))
258 }
259
260 fn register_from<C: AnimationClock>(
261 &mut self,
262 runtime: &mut AnimationRuntime<C>,
263 from: K::Inner,
264 value: K::Inner,
265 ) -> PropertyTransitionRegistration {
266 let replaced = self.active.take();
267 let replaced_handle = replaced.as_ref().map(ActivePropertyTransition::handle);
268
269 self.current = Some(value);
270
271 let registration = runtime.register_property_transition(
272 self.target,
273 self.property,
274 self.timing,
275 from,
276 value,
277 );
278
279 self.active = Some(ActivePropertyTransition::new(
280 registration.handle(),
281 from,
282 value,
283 runtime.clock().now(),
284 self.timing.total_duration(),
285 ));
286
287 self.cleanup_replaced(runtime, replaced);
288
289 PropertyTransitionRegistration::new(registration, replaced_handle)
290 }
291
292 fn cleanup_replaced<C: AnimationClock>(
293 &self,
294 runtime: &mut AnimationRuntime<C>,
295 replaced: Option<ActivePropertyTransition<K>>,
296 ) {
297 if let Some(active) = replaced {
298 runtime.cancel(self.target, active.handle());
299 }
300 }
301
302 fn current_visual_value<C: AnimationClock>(
303 &self,
304 runtime: &AnimationRuntime<C>,
305 ) -> Option<K::Inner> {
306 let active = self.active.as_ref()?;
307 let snapshot = runtime.last_properties(self.target, active.handle())?;
308 let entry = snapshot.find_property(&self.property.raw())?;
309
310 K::unwrap_transition_value(entry.value())
311 }
312
313 fn invalidate_if_stale<C: AnimationClock>(&mut self, runtime: &AnimationRuntime<C>) {
314 if let Some(active) = &self.active
315 && !runtime.contains(self.target, active.handle())
316 {
317 self.active = None;
318 }
319 }
320}