Skip to main content

ferroui_base/animation/
transition_base.rs

1use crate::animation::easings::Easing;
2use crate::animation::{Animatable, IClock, TimeSpan};
3use crate::reactive::IDisposable;
4use crate::{
5    ferro_class, ferro_property, BoxedValue, DirectProperty, FerroObject, FerroObjectImpl, FerroObjectImplExt,
6    FerroProperty, FerroPropertyChangedEventArgs,
7};
8use std::any::TypeId;
9use std::cell::{Cell, RefCell};
10use std::rc::Rc;
11
12/// Defines how a property should be animated using a transition.
13#[repr(C)]
14pub struct TransitionBase {
15    base: FerroObject,
16    duration: Cell<TimeSpan>,
17    delay: Cell<TimeSpan>,
18    easing: RefCell<Easing>,
19    prop: Cell<Option<&'static FerroProperty>>,
20}
21
22ferro_class! {
23    TransitionBase: FerroObject, virtuals TransitionBaseImpl: FerroObjectImpl {
24        /// Applies the transition to `control`: animates the transition's
25        /// property from `old_value` to `new_value`.
26        fn apply(this, control: &Animatable, clock: Rc<dyn IClock>, old_value: &BoxedValue, new_value: &BoxedValue) -> Rc<dyn IDisposable>;
27        /// The type of the values the transition animates, which the value
28        /// type of its property has to match.
29        fn value_type(this) -> (TypeId, &'static str);
30    }
31}
32crate::ferro_class_info!(TransitionBase { interfaces: [std::rc::Rc<dyn crate::animation::ITransition>] });
33
34impl FerroObjectImpl for TransitionBase {
35    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
36        Self::parent_on_property_changed(this, change);
37
38        if change.property() == Self::property_property().as_property() {
39            if let Some(new_value) = change.get_new_value::<Option<&'static FerroProperty>>() {
40                let (value_type, value_type_name) = this.value_type();
41                if new_value.property_type() != value_type {
42                    // The value is rejected after it was stored, as a
43                    // failing change handler leaves it.
44                    panic!(
45                        "Invalid property type \"{}\" for this transition: {}.",
46                        value_type_name,
47                        this.get_type().name()
48                    );
49                }
50            }
51        }
52    }
53}
54
55impl TransitionBaseImpl for TransitionBase {
56    fn apply(
57        _this: &Self,
58        _control: &Animatable,
59        _clock: Rc<dyn IClock>,
60        _old_value: &BoxedValue,
61        _new_value: &BoxedValue,
62    ) -> Rc<dyn IDisposable> {
63        panic!("TransitionBase is abstract: 'apply' must be implemented by the deriving class")
64    }
65
66    fn value_type(_this: &Self) -> (TypeId, &'static str) {
67        panic!("TransitionBase is abstract: 'value_type' must be implemented by the deriving class")
68    }
69}
70
71crate::ferro_properties! { impl TransitionBase {
72    ferro_property!(
73        /// Defines the `Duration` property.
74        pub fn duration_property() -> DirectProperty<TransitionBase, TimeSpan> {
75            FerroProperty::register_direct::<TransitionBase, _>(
76                "Duration",
77                |o| o.duration(),
78                Some(|o, v| o.set_duration(v)),
79                TimeSpan::ZERO,
80            )
81        }
82    );
83
84    ferro_property!(
85        /// Defines the `Delay` property.
86        pub fn delay_property() -> DirectProperty<TransitionBase, TimeSpan> {
87            FerroProperty::register_direct::<TransitionBase, _>(
88                "Delay",
89                |o| o.delay(),
90                Some(|o, v| o.set_delay(v)),
91                TimeSpan::ZERO,
92            )
93        }
94    );
95
96    ferro_property!(
97        /// Defines the `Easing` property.
98        pub fn easing_property() -> DirectProperty<TransitionBase, Easing> {
99            FerroProperty::register_direct::<TransitionBase, _>(
100                "Easing",
101                |o| o.easing(),
102                Some(|o, v| o.set_easing(v)),
103                Easing::default(),
104            )
105        }
106    );
107
108    ferro_property!(
109        /// Defines the `Property` property.
110        pub fn property_property() -> DirectProperty<TransitionBase, Option<&'static FerroProperty>> {
111            FerroProperty::register_direct::<TransitionBase, _>(
112                "Property",
113                |o| o.property(),
114                Some(|o, v| o.set_property(v)),
115                None,
116            )
117        }
118    );
119} }
120
121impl TransitionBase {
122    /// Creates the class data; see [`FerroObject::construct`].
123    pub fn construct() -> Self {
124        Self {
125            base: FerroObject::construct(),
126            duration: Cell::new(TimeSpan::ZERO),
127            delay: Cell::new(TimeSpan::ZERO),
128            easing: RefCell::new(Easing::default()),
129            prop: Cell::new(None),
130        }
131    }
132
133    /// The duration of the transition.
134    pub fn duration(&self) -> TimeSpan {
135        self.duration.get()
136    }
137
138    pub fn set_duration(&self, value: TimeSpan) {
139        self.set_and_raise_cell(Self::duration_property(), &self.duration, value);
140    }
141
142    /// The delay before the transition starts.
143    pub fn delay(&self) -> TimeSpan {
144        self.delay.get()
145    }
146
147    pub fn set_delay(&self, value: TimeSpan) {
148        self.set_and_raise_cell(Self::delay_property(), &self.delay, value);
149    }
150
151    /// The easing class to be used.
152    pub fn easing(&self) -> Easing {
153        self.easing.borrow().clone()
154    }
155
156    pub fn set_easing(&self, value: impl Into<Easing>) {
157        self.set_and_raise(Self::easing_property(), &self.easing, value.into());
158    }
159
160    /// The property to be animated.
161    pub fn property(&self) -> Option<&'static FerroProperty> {
162        self.prop.get()
163    }
164
165    pub fn set_property(&self, value: Option<&'static FerroProperty>) {
166        self.set_and_raise_cell(Self::property_property(), &self.prop, value);
167    }
168
169    /// A description of the transition for diagnostics.
170    pub fn debug_display(&self) -> String {
171        let mut result = self.get_type().name().to_string();
172        if let Some(property) = self.prop.get() {
173            result.push_str(&format!(" Property = {property},"));
174        }
175        result.push_str(&format!(" Duration = {}", self.duration.get()));
176        result
177    }
178}