sequoia_view/
live_view.rs1#![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}