ferroui_base/rendering/composition/animations/
animation_controller.rs1use super::{AnimationInstanceFactory, ICompositionAnimation, ICompositionAnimationBase};
30use crate::rendering::composition::expressions::ExpressionVariant;
31use crate::rendering::composition::server::{
32 CompositionProperty, ServerCompositionPropertySet, ServerCompositor, ServerObjectId,
33};
34use crate::rendering::composition::{
35 AsCompositionObject, CompositionObject, CompositionPropertySet, Compositor, ICompositionObjectAnimations,
36};
37use std::cell::{Cell, RefCell};
38use std::rc::{Rc, Weak};
39
40const PROGRESS: &str = "Progress";
42const PLAYBACK_RATE: &str = "PlaybackRate";
44const IS_PAUSED: &str = "IsPaused";
46const PROGRESS_BEHAVIOR: &str = "ProgressBehavior";
49const PROGRESS_REVISION: &str = "ProgressRevision";
52
53#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
56pub enum AnimationControllerProgressBehavior {
57 #[default]
59 Default,
60 IncludesDelayTime,
62}
63
64pub struct AnimationController {
79 properties: Rc<CompositionPropertySet>,
80 progress_revision: Cell<f32>,
81}
82
83impl AnimationController {
84 pub const MAX_PLAYBACK_RATE: f32 = 1000.0;
86 pub const MIN_PLAYBACK_RATE: f32 = -1000.0;
89
90 pub(crate) fn new(compositor: &Rc<Compositor>) -> Rc<AnimationController> {
91 let properties = CompositionPropertySet::new(compositor);
92 properties.insert_scalar(PROGRESS, 0.0);
93 properties.insert_scalar(PLAYBACK_RATE, 1.0);
94 properties.insert_boolean(IS_PAUSED, false);
95 properties.insert_scalar(PROGRESS_BEHAVIOR, 0.0);
96 properties.insert_scalar(PROGRESS_REVISION, 0.0);
97 Rc::new(AnimationController { properties, progress_revision: Cell::new(0.0) })
98 }
99
100 pub fn compositor(&self) -> &Rc<Compositor> {
102 self.properties.compositor()
103 }
104
105 pub fn pause(&self) {
108 self.properties.insert_boolean(IS_PAUSED, true)
109 }
110
111 pub fn resume(&self) {
113 self.properties.insert_boolean(IS_PAUSED, false)
114 }
115
116 pub fn is_paused(&self) -> bool {
118 self.properties.try_get_boolean(IS_PAUSED).1
119 }
120
121 pub fn playback_rate(&self) -> f32 {
124 self.properties.try_get_scalar(PLAYBACK_RATE).1
125 }
126
127 pub fn set_playback_rate(&self, value: f32) {
130 let value = if value.is_nan() { 1.0 } else { value.clamp(Self::MIN_PLAYBACK_RATE, Self::MAX_PLAYBACK_RATE) };
131 self.properties.insert_scalar(PLAYBACK_RATE, value)
132 }
133
134 pub fn progress(&self) -> f32 {
140 self.properties.try_get_scalar(PROGRESS).1
141 }
142
143 pub fn set_progress(&self, value: f32) {
147 let value = if value.is_nan() { 0.0 } else { value.clamp(0.0, 1.0) };
148 self.properties.insert_scalar(PROGRESS, value);
149 self.progress_revision.set(self.progress_revision.get() + 1.0);
150 self.properties.insert_scalar(PROGRESS_REVISION, self.progress_revision.get());
151 }
152
153 pub fn progress_behavior(&self) -> AnimationControllerProgressBehavior {
155 match self.properties.try_get_scalar(PROGRESS_BEHAVIOR).1 {
156 v if v >= 0.5 => AnimationControllerProgressBehavior::IncludesDelayTime,
157 _ => AnimationControllerProgressBehavior::Default,
158 }
159 }
160
161 pub fn set_progress_behavior(&self, value: AnimationControllerProgressBehavior) {
162 let value = match value {
163 AnimationControllerProgressBehavior::Default => 0.0,
164 AnimationControllerProgressBehavior::IncludesDelayTime => 1.0,
165 };
166 self.properties.insert_scalar(PROGRESS_BEHAVIOR, value)
167 }
168
169 pub(crate) fn server(&self) -> ServerObjectId {
171 self.properties.as_composition_object().required_server()
172 }
173
174 pub(crate) fn wrap(&self, factory: AnimationInstanceFactory) -> AnimationInstanceFactory {
177 let controller = self.server();
178 AnimationInstanceFactory::new(move || {
179 let instance = factory.create();
180 instance.attach_controller(controller);
181 instance
182 })
183 }
184}
185
186impl AsCompositionObject for AnimationController {
187 fn as_composition_object(&self) -> &CompositionObject {
188 self.properties.as_composition_object()
189 }
190
191 fn composition_type_name(&self) -> &'static str {
192 "AnimationController"
193 }
194
195 fn as_property_set(self: Rc<Self>) -> Option<Rc<CompositionPropertySet>> {
198 Some(self.properties.clone())
199 }
200}
201
202impl ICompositionObjectAnimations for AnimationController {
203 fn try_start_animation(
205 &self,
206 property_name: &str,
207 animation: &dyn ICompositionAnimation,
208 final_value: Option<ExpressionVariant>,
209 ) -> bool {
210 if property_name != PROGRESS && property_name != PLAYBACK_RATE {
211 return false;
212 }
213 self.properties.try_start_animation(property_name, animation, final_value)
214 }
215
216 fn get_composition_property(&self, property_name: &str) -> Option<&'static CompositionProperty> {
217 if property_name != PROGRESS && property_name != PLAYBACK_RATE {
218 return None;
219 }
220 self.properties.get_composition_property(property_name)
221 }
222
223 fn composition_object(&self) -> &CompositionObject {
224 self.properties.as_composition_object()
225 }
226}
227
228pub(crate) struct PreparedAnimation {
232 factory: RefCell<Option<AnimationInstanceFactory>>,
233}
234
235impl PreparedAnimation {
236 pub(crate) fn new(factory: AnimationInstanceFactory) -> PreparedAnimation {
237 PreparedAnimation { factory: RefCell::new(Some(factory)) }
238 }
239}
240
241impl ICompositionAnimationBase for PreparedAnimation {
242 fn as_composition_animation(&self) -> Option<&dyn ICompositionAnimation> {
243 Some(self)
244 }
245}
246
247impl ICompositionAnimation for PreparedAnimation {
248 fn target(&self) -> Option<String> {
249 None
250 }
251
252 fn create_instance(&self, _target_object: ServerObjectId, _final_value: Option<ExpressionVariant>) -> AnimationInstanceFactory {
255 match self.factory.borrow_mut().take() {
256 Some(factory) => factory,
257 None => panic!("the instance of a prepared animation is created once"),
258 }
259 }
260}
261
262#[derive(Clone, Copy, Debug)]
266pub(crate) struct ProgressSpan {
267 pub(crate) delay: i64,
268 pub(crate) active: i64,
269}
270
271#[derive(Default)]
276pub(crate) struct AnimationControllerClock {
277 controller: Cell<Option<ServerObjectId>>,
279 resolved: RefCell<Option<Weak<ServerCompositionPropertySet>>>,
280 last_now: Cell<Option<i64>>,
283 time: Cell<f64>,
285 progress_seen: Cell<(f64, f64)>,
287 paused: Cell<bool>,
288}
289
290impl AnimationControllerClock {
291 pub(crate) fn attach(&self, controller: ServerObjectId) {
293 self.controller.set(Some(controller));
294 *self.resolved.borrow_mut() = None;
295 self.last_now.set(None);
296 }
297
298 pub(crate) fn is_paused(&self) -> bool {
300 self.paused.get()
301 }
302
303 fn controller_set(&self, compositor: Option<&Rc<ServerCompositor>>) -> Option<Rc<ServerCompositionPropertySet>> {
304 let id = self.controller.get()?;
305 if let Some(set) = self.resolved.borrow().as_ref().and_then(Weak::upgrade) {
306 return Some(set);
307 }
308 let set = compositor?.get::<ServerCompositionPropertySet>(id)?;
309 *self.resolved.borrow_mut() = Some(Rc::downgrade(&set));
310 Some(set)
311 }
312
313 pub(crate) fn elapsed(
318 &self,
319 compositor: Option<&Rc<ServerCompositor>>,
320 now: i64,
321 started_at: i64,
322 span: Option<ProgressSpan>,
323 ) -> i64 {
324 let raw = now - started_at;
325 if self.controller.get().is_none() {
326 return raw;
327 }
328 let Some(set) = self.controller_set(compositor) else {
329 self.paused.set(false);
332 return match self.last_now.get() {
333 Some(last) => {
334 self.time.set(self.time.get() + (now - last) as f64);
335 self.last_now.set(Some(now));
336 self.time.get() as i64
337 }
338 None => raw,
339 };
340 };
341 let number = |name: &str, default: f64| set.get_value(name).try_cast::<f64>().unwrap_or(default);
342 let paused = set.get_value(IS_PAUSED).try_cast::<bool>().unwrap_or(false);
343 let rate = number(PLAYBACK_RATE, 1.0);
344 let progress = number(PROGRESS, 0.0);
345 let revision = number(PROGRESS_REVISION, 0.0);
346 let includes_delay = number(PROGRESS_BEHAVIOR, 0.0) >= 0.5;
347
348 match self.last_now.get() {
349 None => {
350 self.time.set(raw as f64);
354 self.progress_seen.set((0.0, 0.0));
355 }
356 Some(last) => {
357 if !paused && rate.is_finite() {
358 self.time.set(self.time.get() + (now - last) as f64 * rate);
359 }
360 }
361 }
362 self.last_now.set(Some(now));
363 self.paused.set(paused);
364
365 if self.progress_seen.get() != (progress, revision) {
366 self.progress_seen.set((progress, revision));
367 if let Some(span) = span {
368 let (offset, length) =
369 if includes_delay { (0, span.delay + span.active) } else { (span.delay, span.active) };
370 self.time.set(offset as f64 + progress.clamp(0.0, 1.0) * length as f64);
371 }
372 }
373 let time = self.time.get();
374 if time.is_nan() || time < 0.0 {
375 self.time.set(0.0);
376 }
377 self.time.get() as i64
378 }
379}
380
381crate::ferro_thread_local! {
382 static GLOBAL_PLAYBACK_RATES: RefCell<Vec<(Weak<Compositor>, f32)>> = RefCell::new(Vec::new());
385}
386
387impl Compositor {
388 pub const MAX_GLOBAL_PLAYBACK_RATE: f32 = 1000.0;
390 pub const MIN_GLOBAL_PLAYBACK_RATE: f32 = 0.0;
392
393 pub fn create_animation_controller(&self) -> Rc<AnimationController> {
395 AnimationController::new(&self.this_handle())
396 }
397
398 pub fn global_playback_rate(&self) -> f32 {
401 let this = self as *const Compositor;
402 GLOBAL_PLAYBACK_RATES
403 .with(|rates| rates.borrow().iter().find(|(c, _)| std::ptr::eq(c.as_ptr(), this)).map(|(_, r)| *r))
404 .unwrap_or(1.0)
405 }
406
407 pub fn set_global_playback_rate(&self, value: f32) {
415 let value = if value.is_nan() {
416 1.0
417 } else {
418 value.clamp(Self::MIN_GLOBAL_PLAYBACK_RATE, Self::MAX_GLOBAL_PLAYBACK_RATE)
419 };
420 let this = self.this_handle();
421 GLOBAL_PLAYBACK_RATES.with(|rates| {
422 let mut rates = rates.borrow_mut();
423 rates.retain(|(c, _)| c.strong_count() > 0);
424 match rates.iter_mut().find(|(c, _)| std::ptr::eq(c.as_ptr(), Rc::as_ptr(&this))) {
425 Some(entry) => entry.1 = value,
426 None => rates.push((Rc::downgrade(&this), value)),
427 }
428 });
429 self.post_server_job(
430 move |server| server.animations().set_global_playback_rate(value as f64, server.server_now()),
431 false,
432 );
433 }
434}