sequoia_elements/
progress.rs1use std::time::Instant;
2
3use sequoia_core::{Color, Length, Rect};
4use sequoia_layout::LayoutStyle;
5use sequoia_reactive::Signal;
6use sequoia_view::{AnyView, PaintCtx, Panel, Pulse, Unit, View};
7
8#[derive(Clone)]
9pub struct ProgressBar {
10 value: Signal<f32>,
11 height: f32,
12 track: Option<Color>,
13 fill: Option<Color>,
14}
15
16impl ProgressBar {
17 pub fn new(value: Signal<f32>) -> Self {
18 Self {
19 value,
20 height: 6.0,
21 track: None,
22 fill: None,
23 }
24 }
25
26 pub fn height(mut self, h: f32) -> Self {
27 if h.is_finite() && h > 0.0 {
28 self.height = h;
29 }
30 self
31 }
32
33 pub fn track(mut self, color: Color) -> Self {
34 self.track = Some(color);
35 self
36 }
37
38 pub fn fill(mut self, color: Color) -> Self {
39 self.fill = Some(color);
40 self
41 }
42
43 fn clamped(&self) -> f32 {
44 self.value.get_untracked().clamp(0.0, 1.0)
45 }
46}
47
48impl View for ProgressBar {
49 fn body(&self) -> impl View + 'static {
50 Unit
51 }
52
53 fn layout_style(&self) -> LayoutStyle {
54 LayoutStyle {
55 width: Length::Fill(1.0),
56 height: Length::Px(self.height),
57 ..LayoutStyle::default()
58 }
59 }
60
61 fn paint_self(&self, ctx: &mut PaintCtx) {
62 let _ = self.value.get_untracked();
63 let theme = ctx.theme();
64 let track = self.track.unwrap_or(theme.surface);
65 let fill = self.fill.unwrap_or(theme.accent);
66 let r = ctx.rect;
67 ctx.quad(r, track, r.size.height * 0.5);
68 let w = r.size.width * self.clamped();
69 if w > 0.5 {
70 ctx.quad(
71 Rect::from_xywh(r.left(), r.top(), w, r.size.height),
72 fill,
73 r.size.height * 0.5,
74 );
75 }
76 }
77}
78
79#[derive(Clone)]
80pub struct Spinner {
81 size: f32,
82 color: Option<Color>,
83 period_secs: f32,
84}
85
86impl Spinner {
87 pub fn new() -> Self {
88 Self {
89 size: 16.0,
90 color: None,
91 period_secs: 1.0,
92 }
93 }
94
95 pub fn size(mut self, px: f32) -> Self {
96 if px.is_finite() && px > 0.0 {
97 self.size = px;
98 }
99 self
100 }
101
102 pub fn color(mut self, color: Color) -> Self {
103 self.color = Some(color);
104 self
105 }
106
107 pub fn period_secs(mut self, secs: f32) -> Self {
108 if secs.is_finite() && secs > 0.0 {
109 self.period_secs = secs;
110 }
111 self
112 }
113}
114
115impl Default for Spinner {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121impl View for Spinner {
122 fn body(&self) -> impl View + 'static {
123 Unit
124 }
125
126 fn layout_style(&self) -> LayoutStyle {
127 LayoutStyle {
128 width: Length::Px(self.size),
129 height: Length::Px(self.size),
130 ..LayoutStyle::default()
131 }
132 }
133
134 fn paint_self(&self, ctx: &mut PaintCtx) {
135 let theme = ctx.theme();
136 let base = self.color.unwrap_or(theme.accent);
137 let r = ctx.rect;
138 let cx = r.left() + r.size.width * 0.5;
139 let cy = r.top() + r.size.height * 0.5;
140 let radius = r.size.width.min(r.size.height) * 0.38;
141 let dot = (r.size.width.min(r.size.height) * 0.09).max(1.0);
142 let elapsed = START.elapsed().as_secs_f32();
143 let phase = (elapsed / self.period_secs) % 1.0;
144 for i in 0..8 {
145 let a = (i as f32 / 8.0) * std::f32::consts::TAU;
146
147 let dist = ((i as f32 / 8.0) - phase).rem_euclid(1.0);
148 let alpha = 1.0 - dist * 0.85;
149 ctx.quad(
150 Rect::from_xywh(
151 cx + radius * a.cos() - dot,
152 cy + radius * a.sin() - dot,
153 dot * 2.0,
154 dot * 2.0,
155 ),
156 base.with_alpha(alpha),
157 dot,
158 );
159 }
160 ctx.request_frame();
161 }
162}
163
164static START: std::sync::LazyLock<Instant> = std::sync::LazyLock::new(Instant::now);
165
166#[derive(Clone)]
167pub struct Skeleton {
168 width: Length,
169 height: f32,
170}
171
172impl Default for Skeleton {
173 fn default() -> Self {
174 Self::new()
175 }
176}
177
178impl Skeleton {
179 pub fn new() -> Self {
180 Self {
181 width: Length::Fill(1.0),
182 height: 16.0,
183 }
184 }
185
186 pub fn width(mut self, width: impl Into<Length>) -> Self {
187 self.width = width.into();
188 self
189 }
190
191 pub fn height(mut self, h: f32) -> Self {
192 if h.is_finite() && h > 0.0 {
193 self.height = h;
194 }
195 self
196 }
197}
198
199impl View for Skeleton {
200 fn body(&self) -> impl View + 'static {
201 Unit
202 }
203
204 fn children_override(&self) -> Option<Vec<AnyView>> {
205 Some(vec![AnyView::new(Pulse::new(
206 Panel::new()
207 .width(self.width)
208 .height(Length::Px(self.height)),
209 ))])
210 }
211
212 fn layout_style(&self) -> LayoutStyle {
213 LayoutStyle {
214 width: self.width,
215 height: Length::Px(self.height),
216 ..LayoutStyle::default()
217 }
218 }
219}