Skip to main content

sequoia_view/
live_view.rs

1#![forbid(unsafe_code)]
2
3use sequoia_core::{Color, Length, Vec2};
4use sequoia_layout::LayoutStyle;
5
6use crate::any::AnyView;
7use crate::live::{AnimSpec, Live, Prop, SpringSpec};
8use crate::runtime::{ElementId, Runtime};
9use crate::view::{PaintCtx, View};
10
11#[derive(Clone)]
12pub struct LiveOpacity {
13    child: AnyView,
14    live: Live<f32>,
15}
16
17impl LiveOpacity {
18    pub fn new<V: View + 'static>(child: V, live: impl Into<Live<f32>>) -> Self {
19        Self {
20            child: AnyView::new(child),
21            live: live.into(),
22        }
23    }
24
25    pub fn animate(mut self, spec: impl Into<AnimSpec>) -> Self {
26        self.live.animate_mut(spec);
27        self
28    }
29
30    pub fn spring(mut self, spec: SpringSpec) -> Self {
31        self.live.spring_mut(spec);
32        self
33    }
34}
35
36impl View for LiveOpacity {
37    fn body(&self) -> impl View + 'static {
38        crate::views::Unit
39    }
40
41    fn children_override(&self) -> Option<Vec<AnyView>> {
42        let _ = self.live.target();
43        Some(vec![self.child.clone()])
44    }
45
46    fn layout_style(&self) -> LayoutStyle {
47        LayoutStyle {
48            width: Length::Fit,
49            height: Length::Fit,
50            ..LayoutStyle::default()
51        }
52    }
53
54    fn paint_opacity_ctx(&self, rt: &Runtime, el: ElementId) -> Option<f32> {
55        Some(
56            rt.resolve_live_f32(el, Prop::OPACITY, &self.live)
57                .clamp(0.0, 1.0),
58        )
59    }
60}
61
62#[derive(Clone)]
63pub struct LiveOffset {
64    child: AnyView,
65    live: Live<Vec2>,
66}
67
68impl LiveOffset {
69    pub fn new<V: View + 'static>(child: V, live: impl Into<Live<Vec2>>) -> Self {
70        Self {
71            child: AnyView::new(child),
72            live: live.into(),
73        }
74    }
75
76    pub fn animate(mut self, spec: impl Into<AnimSpec>) -> Self {
77        self.live.animate_mut(spec);
78        self
79    }
80
81    pub fn spring(mut self, spec: SpringSpec) -> Self {
82        self.live.spring_mut(spec);
83        self
84    }
85}
86
87impl View for LiveOffset {
88    fn body(&self) -> impl View + 'static {
89        crate::views::Unit
90    }
91
92    fn children_override(&self) -> Option<Vec<AnyView>> {
93        let _ = self.live.target();
94        Some(vec![self.child.clone()])
95    }
96
97    fn layout_style(&self) -> LayoutStyle {
98        LayoutStyle {
99            width: Length::Fit,
100            height: Length::Fit,
101            ..LayoutStyle::default()
102        }
103    }
104
105    fn paint_offset_ctx(&self, rt: &Runtime, el: ElementId) -> Vec2 {
106        rt.resolve_live_vec2(el, Prop::OFFSET, &self.live)
107    }
108}
109
110#[derive(Clone)]
111pub struct LiveBackground {
112    child: AnyView,
113    live: Live<Color>,
114    radius: f32,
115}
116
117impl LiveBackground {
118    pub fn new<V: View + 'static>(child: V, live: impl Into<Live<Color>>) -> Self {
119        Self {
120            child: AnyView::new(child),
121            live: live.into(),
122            radius: 0.0,
123        }
124    }
125
126    pub fn radius(mut self, radius: f32) -> Self {
127        self.radius = radius.max(0.0);
128        self
129    }
130
131    pub fn animate(mut self, spec: impl Into<AnimSpec>) -> Self {
132        self.live.animate_mut(spec);
133        self
134    }
135}
136
137impl View for LiveBackground {
138    fn body(&self) -> impl View + 'static {
139        crate::views::Unit
140    }
141
142    fn children_override(&self) -> Option<Vec<AnyView>> {
143        let _ = self.live.target();
144        Some(vec![self.child.clone()])
145    }
146
147    fn layout_style(&self) -> LayoutStyle {
148        LayoutStyle {
149            width: Length::Fit,
150            height: Length::Fit,
151            ..LayoutStyle::default()
152        }
153    }
154
155    fn paint_self(&self, ctx: &mut PaintCtx) {
156        let target = self.live.target_untracked();
157        let (color, _) = ctx.runtime.live_color(
158            ctx.element,
159            Prop::BACKGROUND,
160            target,
161            self.live.tween_spec().as_ref(),
162        );
163        if color.a > 0.0 {
164            ctx.quad(ctx.rect, color, self.radius);
165        }
166    }
167}
168
169pub trait LiveExt: View + Sized + 'static {
170    fn opacity_live(self, live: impl Into<Live<f32>>) -> LiveOpacity {
171        LiveOpacity::new(self, live)
172    }
173
174    fn offset_live(self, live: impl Into<Live<Vec2>>) -> LiveOffset {
175        LiveOffset::new(self, live)
176    }
177
178    fn background_live(self, live: impl Into<Live<Color>>) -> LiveBackground {
179        LiveBackground::new(self, live)
180    }
181}
182
183impl<T: View + 'static> LiveExt for T {}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use crate::live::{AnimSpec, SignalLiveExt};
189    use sequoia_animation::Easing;
190    use sequoia_reactive::Signal;
191    use std::time::Duration;
192
193    #[test]
194    fn live_wrappers_track_source() {
195        let s = Signal::new(0.5f32);
196        let w = crate::views::Unit.opacity_live(s.clone().live());
197
198        let kids = w.children_override().expect("jedno dziecko");
199        assert_eq!(kids.len(), 1);
200        s.set(0.8);
201        let kids = w.children_override().expect("jedno dziecko");
202        assert_eq!(kids.len(), 1);
203    }
204
205    #[test]
206    fn animated_live_carries_spec() {
207        let s = Signal::new(8.0f32);
208        let live = s.live().animate(AnimSpec::from_duration(
209            Duration::from_secs_f32(0.15),
210            Easing::Linear,
211        ));
212        assert!(live.is_animated());
213        let w = crate::views::Unit.opacity_live(live);
214        assert!(w.live.is_animated());
215    }
216
217    #[test]
218    fn rebuild_does_not_restart_animation() {
219        use crate::live::Prop;
220        use crate::runtime::{ElementId, Runtime};
221        let rt = Runtime::new();
222
223        let el = ElementId {
224            index: 7,
225            generation: 0,
226        };
227        let spec = AnimSpec::from_duration(Duration::from_secs_f32(10.0), Easing::Linear);
228
229        let w1 = Live::fixed(0.0f32).animate(spec);
230        assert_eq!(rt.resolve_live_f32(el, Prop::OPACITY, &w1), 0.0);
231
232        let w2 = Live::fixed(1.0f32).animate(spec);
233        assert_eq!(rt.resolve_live_f32(el, Prop::OPACITY, &w2), 0.0);
234        assert_eq!(rt.live_anim_count(), 1);
235
236        let w3 = Live::fixed(1.0f32).animate(spec);
237        let _ = rt.resolve_live_f32(el, Prop::BLUR, &w3);
238        assert_eq!(rt.live_anim_count(), 2);
239    }
240}