zng_var/
impls.rs

1//! zng-var depends on zng-[units, txt] so we need to implement these traits here.
2
3use std::{any::Any, borrow::Cow, path::PathBuf, sync::Arc, time::Duration};
4
5use zng_app_context::{app_local, context_local};
6use zng_time::{DInstant, Deadline};
7use zng_txt::Txt;
8use zng_unit::{
9    AngleDegree, AngleGradian, AngleRadian, AngleTurn, ByteLength, CornerRadius2D, Dip, Factor, FactorPercent, FactorUnits, Orientation2D,
10    Px, Rgba, euclid,
11};
12
13use crate::{
14    animation::{Transition, Transitionable, easing::EasingStep},
15    impl_from_and_into_var,
16};
17
18impl Transitionable for f64 {
19    fn lerp(self, to: &Self, step: EasingStep) -> Self {
20        self + (*to - self) * step.0 as f64
21    }
22}
23impl Transitionable for f32 {
24    fn lerp(self, to: &Self, step: EasingStep) -> Self {
25        self + (*to - self) * step.0
26    }
27}
28macro_rules! impl_transitionable {
29    ($FT:ident => $($T:ty,)+) => {$(
30        impl Transitionable for $T {
31            fn lerp(self, to: &Self, step: EasingStep) -> Self {
32                $FT::lerp(self as $FT, &((*to) as $FT), step).round() as _
33            }
34        }
35    )+}
36}
37impl_transitionable! {
38    f32 => i8, u8, i16, u16, i32, u32,
39}
40impl_transitionable! {
41    f64 => u64, i64, u128, i128, isize, usize,
42}
43impl Transitionable for Px {
44    fn lerp(self, to: &Self, step: EasingStep) -> Self {
45        Px(self.0.lerp(&to.0, step))
46    }
47}
48impl Transitionable for Dip {
49    fn lerp(self, to: &Self, step: EasingStep) -> Self {
50        Dip::new_f32(self.to_f32().lerp(&to.to_f32(), step))
51    }
52}
53impl<T, U> Transitionable for euclid::Point2D<T, U>
54where
55    T: Transitionable,
56    U: Send + Sync + Any,
57{
58    fn lerp(self, to: &Self, step: EasingStep) -> Self {
59        euclid::point2(self.x.lerp(&to.x, step), self.y.lerp(&to.y, step))
60    }
61}
62impl<T, U> Transitionable for euclid::Box2D<T, U>
63where
64    T: Transitionable,
65    U: Send + Sync + Any,
66{
67    fn lerp(self, to: &Self, step: EasingStep) -> Self {
68        euclid::Box2D::new(self.min.lerp(&to.min, step), self.max.lerp(&to.max, step))
69    }
70}
71impl<T, U> Transitionable for euclid::Point3D<T, U>
72where
73    T: Transitionable,
74    U: Send + Sync + Any,
75{
76    fn lerp(self, to: &Self, step: EasingStep) -> Self {
77        euclid::point3(self.x.lerp(&to.x, step), self.y.lerp(&to.y, step), self.z.lerp(&to.z, step))
78    }
79}
80impl<T, U> Transitionable for euclid::Box3D<T, U>
81where
82    T: Transitionable,
83    U: Send + Sync + Any,
84{
85    fn lerp(self, to: &Self, step: EasingStep) -> Self {
86        euclid::Box3D::new(self.min.lerp(&to.min, step), self.max.lerp(&to.max, step))
87    }
88}
89impl<T, U> Transitionable for euclid::Length<T, U>
90where
91    T: Transitionable,
92    U: Send + Sync + Any,
93{
94    fn lerp(self, to: &Self, step: EasingStep) -> Self {
95        euclid::Length::new(self.get().lerp(&to.clone().get(), step))
96    }
97}
98impl<T, U> Transitionable for euclid::Size2D<T, U>
99where
100    T: Transitionable,
101    U: Send + Sync + Any,
102{
103    fn lerp(self, to: &Self, step: EasingStep) -> Self {
104        euclid::size2(self.width.lerp(&to.width, step), self.height.lerp(&to.height, step))
105    }
106}
107impl<T, U> Transitionable for euclid::Size3D<T, U>
108where
109    T: Transitionable,
110    U: Send + Sync + Any,
111{
112    fn lerp(self, to: &Self, step: EasingStep) -> Self {
113        euclid::size3(
114            self.width.lerp(&to.width, step),
115            self.height.lerp(&to.height, step),
116            self.depth.lerp(&to.depth, step),
117        )
118    }
119}
120impl<T, U> Transitionable for euclid::Rect<T, U>
121where
122    T: Transitionable,
123    U: Send + Sync + Any,
124{
125    fn lerp(self, to: &Self, step: EasingStep) -> Self {
126        euclid::Rect::new(self.origin.lerp(&to.origin, step), self.size.lerp(&to.size, step))
127    }
128}
129impl<T, U> Transitionable for euclid::Vector2D<T, U>
130where
131    T: Transitionable,
132    U: Send + Sync + Any,
133{
134    fn lerp(self, to: &Self, step: EasingStep) -> Self {
135        euclid::vec2(self.x.lerp(&to.x, step), self.y.lerp(&to.y, step))
136    }
137}
138impl<T, U> Transitionable for euclid::Vector3D<T, U>
139where
140    T: Transitionable,
141    U: Send + Sync + Any,
142{
143    fn lerp(self, to: &Self, step: EasingStep) -> Self {
144        euclid::vec3(self.x.lerp(&to.x, step), self.y.lerp(&to.y, step), self.z.lerp(&to.z, step))
145    }
146}
147impl Transitionable for Factor {
148    fn lerp(self, to: &Self, step: EasingStep) -> Self {
149        Factor(self.0.lerp(&to.0, step))
150    }
151}
152impl<T, U> Transitionable for euclid::SideOffsets2D<T, U>
153where
154    T: Transitionable,
155    U: Send + Sync + Any,
156{
157    fn lerp(self, to: &Self, step: EasingStep) -> Self {
158        euclid::SideOffsets2D::new(
159            self.top.lerp(&to.top, step),
160            self.right.lerp(&to.right, step),
161            self.bottom.lerp(&to.bottom, step),
162            self.left.lerp(&to.left, step),
163        )
164    }
165}
166impl Transitionable for bool {
167    fn lerp(self, to: &Self, step: EasingStep) -> Self {
168        if step >= 1.fct() { *to } else { self }
169    }
170}
171impl<T, U> Transitionable for CornerRadius2D<T, U>
172where
173    T: Transitionable,
174    U: Send + Sync + Any,
175{
176    fn lerp(self, to: &Self, step: EasingStep) -> Self {
177        Self {
178            top_left: self.top_left.lerp(&to.top_left, step),
179            top_right: self.top_right.lerp(&to.top_right, step),
180            bottom_right: self.bottom_right.lerp(&to.bottom_right, step),
181            bottom_left: self.bottom_left.lerp(&to.bottom_left, step),
182        }
183    }
184}
185
186impl Transitionable for ByteLength {
187    fn lerp(self, to: &Self, step: EasingStep) -> Self {
188        Self(self.0.lerp(&to.0, step))
189    }
190}
191
192impl_from_and_into_var! {
193    fn from(s: &'static str) -> Txt;
194    fn from(s: String) -> Txt;
195    fn from(s: Cow<'static, str>) -> Txt;
196    fn from(c: char) -> Txt;
197    fn from(t: Txt) -> PathBuf;
198    fn from(t: Txt) -> String;
199    fn from(t: Txt) -> Cow<'static, str>;
200
201    fn from(f: f32) -> Factor;
202    fn from(one_or_zero: bool) -> Factor;
203    fn from(f: FactorPercent) -> Factor;
204    fn from(f: Factor) -> FactorPercent;
205
206    fn from(d: DInstant) -> Deadline;
207    fn from(d: Duration) -> Deadline;
208
209    fn from(b: usize) -> ByteLength;
210
211    fn from(rad: AngleRadian) -> AngleTurn;
212    fn from(grad: AngleGradian) -> AngleTurn;
213    fn from(deg: AngleDegree) -> AngleTurn;
214
215    fn from(grad: AngleGradian) -> AngleRadian;
216    fn from(deg: AngleDegree) -> AngleRadian;
217    fn from(turn: AngleTurn) -> AngleRadian;
218
219    fn from(rad: AngleRadian) -> AngleGradian;
220    fn from(deg: AngleDegree) -> AngleGradian;
221    fn from(turn: AngleTurn) -> AngleGradian;
222
223    fn from(rad: AngleRadian) -> AngleDegree;
224    fn from(grad: AngleGradian) -> AngleDegree;
225    fn from(turn: AngleTurn) -> AngleDegree;
226}
227
228macro_rules! impl_into_var_option {
229    (
230        $($T:ty),* $(,)?
231    ) => {
232        impl_from_and_into_var! { $(
233            fn from(some: $T) -> Option<$T>;
234        )* }
235    }
236}
237impl_into_var_option! {
238    i8, i16, i32, i64, i128, isize,
239    u8, u16, u32, u64, u128, usize,
240    f32, f64,
241    char, bool,
242    Orientation2D,
243}
244
245/// Spherical linear interpolation sampler.
246///
247/// Animates rotations over the shortest change between angles by modulo wrapping.
248/// A transition from 358º to 1º goes directly to 361º (modulo normalized to 1º).
249///
250/// Types that support this use the [`is_slerp_enabled`] function inside [`Transitionable::lerp`] to change
251/// mode, types that don't support this use the normal linear interpolation. All angle and transform units
252/// implement this.
253///
254/// Samplers can be set in animations using the `Var::easing_with` method.
255pub fn slerp_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
256    slerp_enabled(true, || t.sample(step))
257}
258
259/// Gets if slerp mode is enabled in the context.
260///
261/// See [`slerp_sampler`] for more details.
262pub fn is_slerp_enabled() -> bool {
263    SLERP_ENABLED.get_clone()
264}
265
266/// Calls `f` with [`is_slerp_enabled`] set to `enabled`.
267///
268/// See [`slerp_sampler`] for a way to enable in animations.
269pub fn slerp_enabled<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
270    SLERP_ENABLED.with_context(&mut Some(Arc::new(enabled)), f)
271}
272
273context_local! {
274    static SLERP_ENABLED: bool = false;
275}
276
277impl Transitionable for AngleRadian {
278    fn lerp(self, to: &Self, step: EasingStep) -> Self {
279        match is_slerp_enabled() {
280            false => self.lerp(*to, step),
281            true => self.slerp(*to, step),
282        }
283    }
284}
285impl Transitionable for AngleGradian {
286    fn lerp(self, to: &Self, step: EasingStep) -> Self {
287        match is_slerp_enabled() {
288            false => self.lerp(*to, step),
289            true => self.slerp(*to, step),
290        }
291    }
292}
293impl Transitionable for AngleDegree {
294    fn lerp(self, to: &Self, step: EasingStep) -> Self {
295        match is_slerp_enabled() {
296            false => self.lerp(*to, step),
297            true => self.slerp(*to, step),
298        }
299    }
300}
301impl Transitionable for AngleTurn {
302    fn lerp(self, to: &Self, step: EasingStep) -> Self {
303        match is_slerp_enabled() {
304            false => self.lerp(*to, step),
305            true => self.slerp(*to, step),
306        }
307    }
308}
309impl Transitionable for Rgba {
310    fn lerp(self, to: &Self, step: EasingStep) -> Self {
311        let lerp = *RGBA_LERP.read();
312        lerp(self, *to, step)
313    }
314}
315
316app_local! {
317    /// Implementation of `<Rgba as Transitionable>::lerp`.
318    static RGBA_LERP: fn(Rgba, Rgba, EasingStep) -> Rgba = const { lerp_rgba_linear };
319}
320fn lerp_rgba_linear(mut from: Rgba, to: Rgba, factor: Factor) -> Rgba {
321    from.red = from.red.lerp(&to.red, factor);
322    from.green = from.green.lerp(&to.green, factor);
323    from.blue = from.blue.lerp(&to.blue, factor);
324    from.alpha = from.alpha.lerp(&to.alpha, factor);
325    from
326}
327
328/// API for app implementers to replace the transitionable implementation for foreign types.
329#[expect(non_camel_case_types)]
330pub struct TRANSITIONABLE_APP;
331impl TRANSITIONABLE_APP {
332    /// Replace the [`Rgba`] lerp implementation.
333    ///
334    /// [`Rgba`]: zng_unit::Rgba
335    pub fn init_rgba_lerp(&self, lerp: fn(Rgba, Rgba, EasingStep) -> Rgba) {
336        *RGBA_LERP.write() = lerp;
337    }
338}