Skip to main content

sequoia_view/
layout_live.rs

1#![forbid(unsafe_code)]
2
3use sequoia_core::Length;
4use sequoia_layout::LayoutStyle;
5
6use crate::any::AnyView;
7use crate::live::{AnimSpec, Live, Prop, SpringSpec};
8use crate::runtime::{ElementId, Runtime};
9use crate::view::View;
10
11#[derive(Clone)]
12pub struct AnimatedSize {
13    child: AnyView,
14    width: Option<Live<f32>>,
15    height: Option<Live<f32>>,
16}
17
18impl AnimatedSize {
19    pub fn new<V: View + 'static>(
20        child: V,
21        width: Option<Live<f32>>,
22        height: Option<Live<f32>>,
23    ) -> Self {
24        Self {
25            child: AnyView::new(child),
26            width,
27            height,
28        }
29    }
30
31    pub fn width<V: View + 'static>(child: V, width: impl Into<Live<f32>>) -> Self {
32        Self::new(child, Some(width.into()), None)
33    }
34
35    pub fn height<V: View + 'static>(child: V, height: impl Into<Live<f32>>) -> Self {
36        Self::new(child, None, Some(height.into()))
37    }
38
39    pub fn size<V: View + 'static>(
40        child: V,
41        width: impl Into<Live<f32>>,
42        height: impl Into<Live<f32>>,
43    ) -> Self {
44        Self::new(child, Some(width.into()), Some(height.into()))
45    }
46
47    pub fn animate(mut self, spec: impl Into<AnimSpec>) -> Self {
48        let spec = spec.into();
49        if let Some(w) = &mut self.width {
50            w.animate_mut(spec);
51        }
52        if let Some(h) = &mut self.height {
53            h.animate_mut(spec);
54        }
55        self
56    }
57
58    pub fn spring(mut self, spec: SpringSpec) -> Self {
59        if let Some(w) = &mut self.width {
60            w.spring_mut(spec);
61        }
62        if let Some(h) = &mut self.height {
63            h.spring_mut(spec);
64        }
65        self
66    }
67
68    fn px_or_fit(live: &Option<Live<f32>>) -> Length {
69        match live {
70            Some(l) => Length::Px(l.target_untracked().max(0.0)),
71            None => Length::Fit,
72        }
73    }
74}
75
76impl View for AnimatedSize {
77    fn body(&self) -> impl View + 'static {
78        crate::views::Unit
79    }
80
81    fn children_override(&self) -> Option<Vec<AnyView>> {
82        if let Some(w) = &self.width {
83            let _ = w.target();
84        }
85        if let Some(h) = &self.height {
86            let _ = h.target();
87        }
88        Some(vec![self.child.clone()])
89    }
90
91    fn layout_style(&self) -> LayoutStyle {
92        LayoutStyle {
93            width: Self::px_or_fit(&self.width),
94            height: Self::px_or_fit(&self.height),
95            ..LayoutStyle::default()
96        }
97    }
98
99    fn layout_style_ctx(&self, rt: &Runtime, el: ElementId) -> LayoutStyle {
100        let width = match &self.width {
101            Some(live) => Length::Px(rt.resolve_live_f32(el, Prop::WIDTH, live).max(0.0)),
102            None => Length::Fit,
103        };
104        let height = match &self.height {
105            Some(live) => Length::Px(rt.resolve_live_f32(el, Prop::HEIGHT, live).max(0.0)),
106            None => Length::Fit,
107        };
108        LayoutStyle {
109            width,
110            height,
111            ..LayoutStyle::default()
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use crate::live::SignalLiveExt;
120    use sequoia_reactive::Signal;
121    use std::time::Duration;
122
123    #[test]
124    fn static_snapshot_without_runtime() {
125        let w = AnimatedSize::width(crate::views::Unit, 120.0f32);
126        let s = w.layout_style();
127        assert_eq!(s.width, Length::Px(120.0));
128        assert_eq!(s.height, Length::Fit);
129    }
130
131    #[test]
132    fn tracks_signal_target() {
133        let sig = Signal::new(80.0f32);
134        let w = AnimatedSize::width(crate::views::Unit, sig.clone().live());
135        let kids = w.children_override().expect("dziecko");
136        assert_eq!(kids.len(), 1);
137        sig.set(200.0);
138        let s = w.layout_style();
139        assert_eq!(s.width, Length::Px(200.0));
140    }
141
142    #[test]
143    fn runtime_anchors_then_animates() {
144        let rt = Runtime::new();
145        let el = ElementId {
146            index: 91,
147            generation: 0,
148        };
149        let spec = AnimSpec::from_duration(
150            Duration::from_secs_f32(10.0),
151            sequoia_animation::Easing::Linear,
152        );
153        let live = Live::fixed(80.0f32).animate(spec);
154        let w = AnimatedSize::width(crate::views::Unit, live);
155
156        let s1 = w.layout_style_ctx(&rt, el);
157        assert_eq!(s1.width, Length::Px(80.0));
158
159        let live2 = Live::fixed(200.0f32).animate(spec);
160        let w2 = AnimatedSize::width(crate::views::Unit, live2);
161        let s2 = w2.layout_style_ctx(&rt, el);
162        assert_eq!(s2.width, Length::Px(80.0));
163        assert_eq!(rt.live_anim_count(), 1);
164    }
165
166    #[test]
167    fn size_sets_both_axes() {
168        let w = AnimatedSize::size(crate::views::Unit, 100.0f32, 40.0f32);
169        let s = w.layout_style();
170        assert_eq!(s.width, Length::Px(100.0));
171        assert_eq!(s.height, Length::Px(40.0));
172        let w = w.animate(AnimSpec::FAST).spring(SpringSpec::BOUNCY);
173        let s = w.layout_style();
174        assert_eq!(s.width, Length::Px(100.0));
175    }
176}