Skip to main content

inset_rendering/
tweens.rs

1//! Flutter counterpart: `rendering/tweens.dart`.
2
3use inset_animation::Animatable;
4use inset_foundation::App;
5use inset_painting::{Alignment, AlignmentGeometry, FractionalOffset};
6
7/// An interpolation between two fractional offsets.
8///
9/// This class specializes the interpolation of `Tween<FractionalOffset>` to be
10/// appropriate for fractional offsets.
11///
12/// See `Tween` for a discussion on how to use interpolation objects.
13///
14/// Unlike the tweens in `inset_animation`, this one is a value rather than an arena object:
15/// the orphan rule leaves no local type in `impl Animatable<Option<FractionalOffset>> for
16/// Handle<FractionalOffsetTween>`. A driven animation therefore holds a clone, and writing
17/// [`begin`](Self::begin) or [`end`](Self::end) afterwards does not reach it.
18///
19/// See also:
20///
21///  * [`AlignmentTween`], which interpolates between two [`Alignment`] objects.
22#[derive(Clone, Debug)]
23pub struct FractionalOffsetTween {
24    /// The value this variable has at the beginning of the animation.
25    pub begin: Option<FractionalOffset>,
26
27    /// The value this variable has at the end of the animation.
28    pub end: Option<FractionalOffset>,
29}
30
31impl FractionalOffsetTween {
32    /// Creates a fractional offset tween.
33    ///
34    /// The [`begin`](Self::begin) and [`end`](Self::end) properties may be `None`; the
35    /// `None` value is treated as meaning the center.
36    pub fn new(
37        begin: Option<FractionalOffset>,
38        end: Option<FractionalOffset>,
39    ) -> FractionalOffsetTween {
40        FractionalOffsetTween { begin, end }
41    }
42
43    /// Returns the value this variable has at the given animation clock value.
44    pub fn lerp(&self, t: f64) -> Option<FractionalOffset> {
45        FractionalOffset::lerp(self.begin, self.end, t)
46    }
47}
48
49impl Animatable<Option<FractionalOffset>> for FractionalOffsetTween {
50    fn transform(&self, _app: &App, t: f64) -> Option<FractionalOffset> {
51        if t == 0.0 {
52            return self.begin;
53        }
54        if t == 1.0 {
55            return self.end;
56        }
57        self.lerp(t)
58    }
59}
60
61/// An interpolation between two alignments.
62///
63/// This class specializes the interpolation of `Tween<Alignment>` to be
64/// appropriate for alignments.
65///
66/// See `Tween` for a discussion on how to use interpolation objects.
67///
68/// Like [`FractionalOffsetTween`], this one is a value rather than an arena object (see
69/// PORTING.md).
70///
71/// See also:
72///
73///  * [`AlignmentGeometryTween`], which interpolates between two
74///    [`AlignmentGeometry`] objects.
75#[derive(Clone, Debug)]
76pub struct AlignmentTween {
77    /// The value this variable has at the beginning of the animation.
78    pub begin: Option<Alignment>,
79
80    /// The value this variable has at the end of the animation.
81    pub end: Option<Alignment>,
82}
83
84impl AlignmentTween {
85    /// Creates a fractional offset tween.
86    ///
87    /// The [`begin`](Self::begin) and [`end`](Self::end) properties may be `None`; the
88    /// `None` value is treated as meaning the center.
89    pub fn new(begin: Option<Alignment>, end: Option<Alignment>) -> AlignmentTween {
90        AlignmentTween { begin, end }
91    }
92
93    /// Returns the value this variable has at the given animation clock value.
94    pub fn lerp(&self, t: f64) -> Alignment {
95        Alignment::lerp(self.begin, self.end, t)
96            .expect("AlignmentTween.begin or end must be set before use")
97    }
98}
99
100impl Animatable<Alignment> for AlignmentTween {
101    fn transform(&self, _app: &App, t: f64) -> Alignment {
102        if t == 0.0 {
103            return self.begin.expect("Tween.begin must be set before use");
104        }
105        if t == 1.0 {
106            return self.end.expect("Tween.end must be set before use");
107        }
108        self.lerp(t)
109    }
110}
111
112/// An interpolation between two [`AlignmentGeometry`].
113///
114/// This class specializes the interpolation of `Tween<AlignmentGeometry>` to be
115/// appropriate for alignments.
116///
117/// See `Tween` for a discussion on how to use interpolation objects.
118///
119/// Like [`FractionalOffsetTween`], this one is a value rather than an arena object (see
120/// PORTING.md).
121///
122/// See also:
123///
124///  * [`AlignmentTween`], which interpolates between two [`Alignment`] objects.
125#[derive(Clone, Debug)]
126pub struct AlignmentGeometryTween {
127    /// The value this variable has at the beginning of the animation.
128    pub begin: Option<AlignmentGeometry>,
129
130    /// The value this variable has at the end of the animation.
131    pub end: Option<AlignmentGeometry>,
132}
133
134impl AlignmentGeometryTween {
135    /// Creates a fractional offset geometry tween.
136    ///
137    /// The [`begin`](Self::begin) and [`end`](Self::end) properties may be `None`; the
138    /// `None` value is treated as meaning the center.
139    pub fn new(
140        begin: Option<AlignmentGeometry>,
141        end: Option<AlignmentGeometry>,
142    ) -> AlignmentGeometryTween {
143        AlignmentGeometryTween { begin, end }
144    }
145
146    /// Returns the value this variable has at the given animation clock value.
147    pub fn lerp(&self, t: f64) -> Option<AlignmentGeometry> {
148        AlignmentGeometry::lerp(self.begin, self.end, t)
149    }
150}
151
152impl Animatable<Option<AlignmentGeometry>> for AlignmentGeometryTween {
153    fn transform(&self, _app: &App, t: f64) -> Option<AlignmentGeometry> {
154        if t == 0.0 {
155            return self.begin;
156        }
157        if t == 1.0 {
158            return self.end;
159        }
160        self.lerp(t)
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use inset_foundation::AppCell;
167    use inset_painting::{AlignmentDirectional, TextDirection};
168
169    use super::*;
170
171    #[test]
172    fn an_alignment_tween_walks_between_the_corners() {
173        let cell = AppCell::new();
174        let app = cell.borrow();
175        let tween = AlignmentTween::new(Some(Alignment::TOP_LEFT), Some(Alignment::BOTTOM_LEFT));
176        assert_eq!(tween.transform(&app, 0.0), Alignment::TOP_LEFT);
177        assert_eq!(tween.transform(&app, 0.5), Alignment::CENTER_LEFT);
178        assert_eq!(tween.transform(&app, 1.0), Alignment::BOTTOM_LEFT);
179    }
180
181    #[test]
182    fn an_alignment_geometry_tween_crosses_the_two_kinds() {
183        let cell = AppCell::new();
184        let app = cell.borrow();
185        let tween = AlignmentGeometryTween::new(
186            Some(Alignment::CENTER_RIGHT.into()),
187            Some(AlignmentDirectional::CENTER_END.into()),
188        );
189        let halfway = tween.transform(&app, 0.5).expect("both ends are set");
190        assert_eq!(
191            halfway.resolve(Some(TextDirection::Ltr)),
192            Alignment::CENTER_RIGHT
193        );
194        assert_eq!(halfway.resolve(Some(TextDirection::Rtl)), Alignment::CENTER);
195    }
196
197    #[test]
198    fn a_fractional_offset_tween_walks_between_the_corners() {
199        let cell = AppCell::new();
200        let app = cell.borrow();
201        let tween = FractionalOffsetTween::new(
202            Some(FractionalOffset::new(0.0, 0.0)),
203            Some(FractionalOffset::new(1.0, 1.0)),
204        );
205        assert_eq!(
206            tween.transform(&app, 0.25),
207            Some(FractionalOffset::new(0.25, 0.25))
208        );
209    }
210}