use super::{AnimationInstanceFactory, ICompositionAnimation, ICompositionAnimationBase};
use crate::rendering::composition::expressions::ExpressionVariant;
use crate::rendering::composition::server::{
CompositionProperty, ServerCompositionPropertySet, ServerCompositor, ServerObjectId,
};
use crate::rendering::composition::{
AsCompositionObject, CompositionObject, CompositionPropertySet, Compositor, ICompositionObjectAnimations,
};
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
const PROGRESS: &str = "Progress";
const PLAYBACK_RATE: &str = "PlaybackRate";
const IS_PAUSED: &str = "IsPaused";
const PROGRESS_BEHAVIOR: &str = "ProgressBehavior";
const PROGRESS_REVISION: &str = "ProgressRevision";
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum AnimationControllerProgressBehavior {
#[default]
Default,
IncludesDelayTime,
}
pub struct AnimationController {
properties: Rc<CompositionPropertySet>,
progress_revision: Cell<f32>,
}
impl AnimationController {
pub const MAX_PLAYBACK_RATE: f32 = 1000.0;
pub const MIN_PLAYBACK_RATE: f32 = -1000.0;
pub(crate) fn new(compositor: &Rc<Compositor>) -> Rc<AnimationController> {
let properties = CompositionPropertySet::new(compositor);
properties.insert_scalar(PROGRESS, 0.0);
properties.insert_scalar(PLAYBACK_RATE, 1.0);
properties.insert_boolean(IS_PAUSED, false);
properties.insert_scalar(PROGRESS_BEHAVIOR, 0.0);
properties.insert_scalar(PROGRESS_REVISION, 0.0);
Rc::new(AnimationController { properties, progress_revision: Cell::new(0.0) })
}
pub fn compositor(&self) -> &Rc<Compositor> {
self.properties.compositor()
}
pub fn pause(&self) {
self.properties.insert_boolean(IS_PAUSED, true)
}
pub fn resume(&self) {
self.properties.insert_boolean(IS_PAUSED, false)
}
pub fn is_paused(&self) -> bool {
self.properties.try_get_boolean(IS_PAUSED).1
}
pub fn playback_rate(&self) -> f32 {
self.properties.try_get_scalar(PLAYBACK_RATE).1
}
pub fn set_playback_rate(&self, value: f32) {
let value = if value.is_nan() { 1.0 } else { value.clamp(Self::MIN_PLAYBACK_RATE, Self::MAX_PLAYBACK_RATE) };
self.properties.insert_scalar(PLAYBACK_RATE, value)
}
pub fn progress(&self) -> f32 {
self.properties.try_get_scalar(PROGRESS).1
}
pub fn set_progress(&self, value: f32) {
let value = if value.is_nan() { 0.0 } else { value.clamp(0.0, 1.0) };
self.properties.insert_scalar(PROGRESS, value);
self.progress_revision.set(self.progress_revision.get() + 1.0);
self.properties.insert_scalar(PROGRESS_REVISION, self.progress_revision.get());
}
pub fn progress_behavior(&self) -> AnimationControllerProgressBehavior {
match self.properties.try_get_scalar(PROGRESS_BEHAVIOR).1 {
v if v >= 0.5 => AnimationControllerProgressBehavior::IncludesDelayTime,
_ => AnimationControllerProgressBehavior::Default,
}
}
pub fn set_progress_behavior(&self, value: AnimationControllerProgressBehavior) {
let value = match value {
AnimationControllerProgressBehavior::Default => 0.0,
AnimationControllerProgressBehavior::IncludesDelayTime => 1.0,
};
self.properties.insert_scalar(PROGRESS_BEHAVIOR, value)
}
pub(crate) fn server(&self) -> ServerObjectId {
self.properties.as_composition_object().required_server()
}
pub(crate) fn wrap(&self, factory: AnimationInstanceFactory) -> AnimationInstanceFactory {
let controller = self.server();
AnimationInstanceFactory::new(move || {
let instance = factory.create();
instance.attach_controller(controller);
instance
})
}
}
impl AsCompositionObject for AnimationController {
fn as_composition_object(&self) -> &CompositionObject {
self.properties.as_composition_object()
}
fn composition_type_name(&self) -> &'static str {
"AnimationController"
}
fn as_property_set(self: Rc<Self>) -> Option<Rc<CompositionPropertySet>> {
Some(self.properties.clone())
}
}
impl ICompositionObjectAnimations for AnimationController {
fn try_start_animation(
&self,
property_name: &str,
animation: &dyn ICompositionAnimation,
final_value: Option<ExpressionVariant>,
) -> bool {
if property_name != PROGRESS && property_name != PLAYBACK_RATE {
return false;
}
self.properties.try_start_animation(property_name, animation, final_value)
}
fn get_composition_property(&self, property_name: &str) -> Option<&'static CompositionProperty> {
if property_name != PROGRESS && property_name != PLAYBACK_RATE {
return None;
}
self.properties.get_composition_property(property_name)
}
fn composition_object(&self) -> &CompositionObject {
self.properties.as_composition_object()
}
}
pub(crate) struct PreparedAnimation {
factory: RefCell<Option<AnimationInstanceFactory>>,
}
impl PreparedAnimation {
pub(crate) fn new(factory: AnimationInstanceFactory) -> PreparedAnimation {
PreparedAnimation { factory: RefCell::new(Some(factory)) }
}
}
impl ICompositionAnimationBase for PreparedAnimation {
fn as_composition_animation(&self) -> Option<&dyn ICompositionAnimation> {
Some(self)
}
}
impl ICompositionAnimation for PreparedAnimation {
fn target(&self) -> Option<String> {
None
}
fn create_instance(&self, _target_object: ServerObjectId, _final_value: Option<ExpressionVariant>) -> AnimationInstanceFactory {
match self.factory.borrow_mut().take() {
Some(factory) => factory,
None => panic!("the instance of a prepared animation is created once"),
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct ProgressSpan {
pub(crate) delay: i64,
pub(crate) active: i64,
}
#[derive(Default)]
pub(crate) struct AnimationControllerClock {
controller: Cell<Option<ServerObjectId>>,
resolved: RefCell<Option<Weak<ServerCompositionPropertySet>>>,
last_now: Cell<Option<i64>>,
time: Cell<f64>,
progress_seen: Cell<(f64, f64)>,
paused: Cell<bool>,
}
impl AnimationControllerClock {
pub(crate) fn attach(&self, controller: ServerObjectId) {
self.controller.set(Some(controller));
*self.resolved.borrow_mut() = None;
self.last_now.set(None);
}
pub(crate) fn is_paused(&self) -> bool {
self.paused.get()
}
fn controller_set(&self, compositor: Option<&Rc<ServerCompositor>>) -> Option<Rc<ServerCompositionPropertySet>> {
let id = self.controller.get()?;
if let Some(set) = self.resolved.borrow().as_ref().and_then(Weak::upgrade) {
return Some(set);
}
let set = compositor?.get::<ServerCompositionPropertySet>(id)?;
*self.resolved.borrow_mut() = Some(Rc::downgrade(&set));
Some(set)
}
pub(crate) fn elapsed(
&self,
compositor: Option<&Rc<ServerCompositor>>,
now: i64,
started_at: i64,
span: Option<ProgressSpan>,
) -> i64 {
let raw = now - started_at;
if self.controller.get().is_none() {
return raw;
}
let Some(set) = self.controller_set(compositor) else {
self.paused.set(false);
return match self.last_now.get() {
Some(last) => {
self.time.set(self.time.get() + (now - last) as f64);
self.last_now.set(Some(now));
self.time.get() as i64
}
None => raw,
};
};
let number = |name: &str, default: f64| set.get_value(name).try_cast::<f64>().unwrap_or(default);
let paused = set.get_value(IS_PAUSED).try_cast::<bool>().unwrap_or(false);
let rate = number(PLAYBACK_RATE, 1.0);
let progress = number(PROGRESS, 0.0);
let revision = number(PROGRESS_REVISION, 0.0);
let includes_delay = number(PROGRESS_BEHAVIOR, 0.0) >= 0.5;
match self.last_now.get() {
None => {
self.time.set(raw as f64);
self.progress_seen.set((0.0, 0.0));
}
Some(last) => {
if !paused && rate.is_finite() {
self.time.set(self.time.get() + (now - last) as f64 * rate);
}
}
}
self.last_now.set(Some(now));
self.paused.set(paused);
if self.progress_seen.get() != (progress, revision) {
self.progress_seen.set((progress, revision));
if let Some(span) = span {
let (offset, length) =
if includes_delay { (0, span.delay + span.active) } else { (span.delay, span.active) };
self.time.set(offset as f64 + progress.clamp(0.0, 1.0) * length as f64);
}
}
let time = self.time.get();
if time.is_nan() || time < 0.0 {
self.time.set(0.0);
}
self.time.get() as i64
}
}
crate::ferro_thread_local! {
static GLOBAL_PLAYBACK_RATES: RefCell<Vec<(Weak<Compositor>, f32)>> = RefCell::new(Vec::new());
}
impl Compositor {
pub const MAX_GLOBAL_PLAYBACK_RATE: f32 = 1000.0;
pub const MIN_GLOBAL_PLAYBACK_RATE: f32 = 0.0;
pub fn create_animation_controller(&self) -> Rc<AnimationController> {
AnimationController::new(&self.this_handle())
}
pub fn global_playback_rate(&self) -> f32 {
let this = self as *const Compositor;
GLOBAL_PLAYBACK_RATES
.with(|rates| rates.borrow().iter().find(|(c, _)| std::ptr::eq(c.as_ptr(), this)).map(|(_, r)| *r))
.unwrap_or(1.0)
}
pub fn set_global_playback_rate(&self, value: f32) {
let value = if value.is_nan() {
1.0
} else {
value.clamp(Self::MIN_GLOBAL_PLAYBACK_RATE, Self::MAX_GLOBAL_PLAYBACK_RATE)
};
let this = self.this_handle();
GLOBAL_PLAYBACK_RATES.with(|rates| {
let mut rates = rates.borrow_mut();
rates.retain(|(c, _)| c.strong_count() > 0);
match rates.iter_mut().find(|(c, _)| std::ptr::eq(c.as_ptr(), Rc::as_ptr(&this))) {
Some(entry) => entry.1 = value,
None => rates.push((Rc::downgrade(&this), value)),
}
});
self.post_server_job(
move |server| server.animations().set_global_playback_rate(value as f64, server.server_now()),
false,
);
}
}