ferroui_base/rendering/composition/animations/
natural_motion_animation.rs1use super::{
21 AnimationDelayBehavior, AnimationInstanceFactory, AnimationStopBehavior, CompositionAnimation,
22 ICompositionAnimation, ICompositionAnimationBase, SpringNaturalMotionAnimationInstance,
23 SpringNaturalMotionParameters,
24};
25use crate::numerics::{Vector2, Vector3};
26use crate::rendering::composition::expressions::ExpressionVariant;
27use crate::rendering::composition::server::ServerObjectId;
28use crate::rendering::composition::{AsCompositionObject, CompositionObject, Compositor};
29use std::cell::Cell;
30use std::ops::Deref;
31use std::rc::Rc;
32use std::time::Duration;
33
34pub const DEFAULT_SPRING_DAMPING_RATIO: f32 = 1.0;
40
41pub const DEFAULT_SPRING_PERIOD: Duration = Duration::from_millis(50);
43
44pub struct NaturalMotionAnimation {
47 base: CompositionAnimation,
48 delay_behavior: Cell<AnimationDelayBehavior>,
49 delay_time: Cell<Duration>,
50 stop_behavior: Cell<AnimationStopBehavior>,
51}
52
53impl Deref for NaturalMotionAnimation {
54 type Target = CompositionAnimation;
55
56 fn deref(&self) -> &CompositionAnimation {
57 &self.base
58 }
59}
60
61impl NaturalMotionAnimation {
62 fn new(compositor: &Rc<Compositor>) -> NaturalMotionAnimation {
63 NaturalMotionAnimation {
64 base: CompositionAnimation::new(compositor),
65 delay_behavior: Cell::new(AnimationDelayBehavior::default()),
66 delay_time: Cell::new(Duration::ZERO),
67 stop_behavior: Cell::new(AnimationStopBehavior::default()),
68 }
69 }
70
71 pub fn delay_behavior(&self) -> AnimationDelayBehavior {
74 self.delay_behavior.get()
75 }
76
77 pub fn set_delay_behavior(&self, value: AnimationDelayBehavior) {
78 self.delay_behavior.set(value)
79 }
80
81 pub fn delay_time(&self) -> Duration {
84 self.delay_time.get()
85 }
86
87 pub fn set_delay_time(&self, value: Duration) {
88 self.delay_time.set(value)
89 }
90
91 pub fn stop_behavior(&self) -> AnimationStopBehavior {
94 self.stop_behavior.get()
95 }
96
97 pub fn set_stop_behavior(&self, value: AnimationStopBehavior) {
98 self.stop_behavior.set(value)
99 }
100}
101
102macro_rules! natural_motion_classes {
105 ($(($motion:ident, $spring:ident, $create:ident, $ty:ty, $type_name:literal)),* $(,)?) => {$(
106 #[doc = concat!(
107 "A natural motion animation of ", $type_name, " values (WinUI `", stringify!($motion), "`): ",
108 "where the motion starts and ends, and its initial velocity."
109 )]
110 pub struct $motion {
111 base: NaturalMotionAnimation,
112 final_value: Cell<Option<$ty>>,
113 initial_value: Cell<Option<$ty>>,
114 initial_velocity: Cell<$ty>,
115 }
116
117 impl Deref for $motion {
118 type Target = NaturalMotionAnimation;
119
120 fn deref(&self) -> &NaturalMotionAnimation {
121 &self.base
122 }
123 }
124
125 impl $motion {
126 fn new(compositor: &Rc<Compositor>) -> $motion {
127 $motion {
128 base: NaturalMotionAnimation::new(compositor),
129 final_value: Cell::new(None),
130 initial_value: Cell::new(None),
131 initial_velocity: Cell::new(<$ty>::default()),
132 }
133 }
134
135 pub fn final_value(&self) -> Option<$ty> {
139 self.final_value.get()
140 }
141
142 pub fn set_final_value(&self, value: Option<$ty>) {
143 self.final_value.set(value)
144 }
145
146 pub fn initial_value(&self) -> Option<$ty> {
149 self.initial_value.get()
150 }
151
152 pub fn set_initial_value(&self, value: Option<$ty>) {
153 self.initial_value.set(value)
154 }
155
156 pub fn initial_velocity(&self) -> $ty {
159 self.initial_velocity.get()
160 }
161
162 pub fn set_initial_velocity(&self, value: $ty) {
163 self.initial_velocity.set(value)
164 }
165 }
166
167 #[doc = concat!(
168 "A spring animation of ", $type_name, " values (WinUI `", stringify!($spring), "`): ",
169 "each component moves on a damped harmonic oscillator of the given damping ratio and period."
170 )]
171 pub struct $spring {
172 base: $motion,
173 damping_ratio: Cell<f32>,
174 period: Cell<Duration>,
175 }
176
177 impl Deref for $spring {
178 type Target = $motion;
179
180 fn deref(&self) -> &$motion {
181 &self.base
182 }
183 }
184
185 impl $spring {
186 pub fn new(compositor: &Rc<Compositor>) -> Rc<$spring> {
187 Rc::new($spring {
188 base: $motion::new(compositor),
189 damping_ratio: Cell::new(DEFAULT_SPRING_DAMPING_RATIO),
190 period: Cell::new(DEFAULT_SPRING_PERIOD),
191 })
192 }
193
194 pub fn damping_ratio(&self) -> f32 {
198 self.damping_ratio.get()
199 }
200
201 pub fn set_damping_ratio(&self, value: f32) {
204 if value.is_nan() || value < 0.0 || value.is_infinite() {
205 panic!("The damping ratio must be a non-negative number");
206 }
207 self.damping_ratio.set(value)
208 }
209
210 pub fn period(&self) -> Duration {
213 self.period.get()
214 }
215
216 pub fn set_period(&self, value: Duration) {
218 if value.is_zero() {
219 panic!("The period of a spring must be positive");
220 }
221 self.period.set(value)
222 }
223 }
224
225 impl ICompositionAnimationBase for $spring {
226 fn as_composition_animation(&self) -> Option<&dyn ICompositionAnimation> {
227 Some(self)
228 }
229 }
230
231 impl ICompositionAnimation for $spring {
232 fn target(&self) -> Option<String> {
233 self.base.target()
234 }
235
236 fn create_instance(
237 &self,
238 target_object: ServerObjectId,
239 implicit_final_value: Option<ExpressionVariant>,
240 ) -> AnimationInstanceFactory {
241 let parameters = self.create_snapshot_source();
242 let initial_values = match self.initial_value_expressions().parse_all() {
245 Ok(initial_values) => initial_values,
246 Err(error) => panic!("{error}"),
247 };
248 let spring = SpringNaturalMotionParameters::<$ty> {
249 final_value: self
250 .base
251 .final_value()
252 .or_else(|| implicit_final_value.and_then(|value| value.try_cast::<$ty>())),
253 initial_value: self.base.initial_value(),
254 initial_velocity: self.base.initial_velocity(),
255 damping_ratio: self.damping_ratio(),
256 period: self.period(),
257 delay_behavior: self.delay_behavior(),
258 delay_time: self.delay_time(),
259 stop_behavior: self.stop_behavior(),
260 };
261 let completion = self.compositor().acquire_animation_completion(spring.damping_ratio <= 0.0);
264 AnimationInstanceFactory::new(move || {
265 let instance = SpringNaturalMotionAnimationInstance::<$ty>::new(
266 spring,
267 target_object,
268 Rc::new(parameters.build()),
269 );
270 instance.set_completion(completion);
271 instance.set_initial_value_expressions(initial_values);
272 instance
273 })
274 }
275 }
276
277 impl AsCompositionObject for $spring {
278 fn as_composition_object(&self) -> &CompositionObject {
279 self.object()
280 }
281
282 fn composition_type_name(&self) -> &'static str {
283 stringify!($spring)
284 }
285 }
286
287 impl Compositor {
288 #[doc = concat!("Creates a `", stringify!($spring), "`.")]
289 pub fn $create(&self) -> Rc<$spring> {
290 $spring::new(&self.this_handle())
291 }
292 }
293 )*};
294}
295
296natural_motion_classes! {
297 (ScalarNaturalMotionAnimation, SpringScalarNaturalMotionAnimation, create_spring_scalar_animation, f32, "scalar"),
298 (Vector2NaturalMotionAnimation, SpringVector2NaturalMotionAnimation, create_spring_vector2_animation, Vector2, "`Vector2`"),
299 (Vector3NaturalMotionAnimation, SpringVector3NaturalMotionAnimation, create_spring_vector3_animation, Vector3, "`Vector3`"),
300}