1use frus_core::{Color, Rect, Scene};
5use frus_layout::{Dimension, Style};
6
7use crate::interaction::Status;
8use crate::theme::Theme;
9use crate::widget::Widget;
10
11const SPEED: f32 = 2.5;
13
14pub struct Skeleton {
16 width: Dimension,
17 height: f32,
18 radius: f32,
19}
20
21impl Skeleton {
22 pub fn new() -> Self {
24 Self {
25 width: Dimension::Auto,
26 height: 16.0,
27 radius: 6.0,
28 }
29 }
30
31 pub fn width(mut self, width: f32) -> Self {
33 self.width = Dimension::Length(width);
34 self
35 }
36
37 pub fn height(mut self, height: f32) -> Self {
39 self.height = height;
40 self
41 }
42
43 pub fn radius(mut self, radius: f32) -> Self {
45 self.radius = radius;
46 self
47 }
48}
49
50impl Default for Skeleton {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56impl<Msg> Widget<Msg> for Skeleton {
57 fn style(&self) -> Style {
58 Style {
59 width: self.width,
60 height: Dimension::Length(self.height),
61 ..Default::default()
62 }
63 }
64
65 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
66 &[]
67 }
68
69 fn continuous(&self) -> bool {
70 true
71 }
72
73 fn paint(&self, bounds: Rect, status: Status, theme: &Theme, scene: &mut Scene) {
74 let o = status.opacity;
75 let pulse = 0.5 + 0.5 * (status.time * SPEED).sin();
77 let alpha = (0.18 + 0.22 * pulse) * o;
78 scene.draw_rect(
79 bounds,
80 theme.muted.fade(alpha),
81 self.radius,
82 0.0,
83 Color::TRANSPARENT,
84 );
85 }
86
87 fn on_click(&self) -> Option<Msg> {
88 None
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use frus_core::Primitive;
96
97 fn alpha_at(time: f32) -> f32 {
98 let sk = Skeleton::new().width(120.0);
99 let status = Status {
100 time,
101 ..Default::default()
102 };
103 let mut scene = Scene::new();
104 Widget::<()>::paint(
105 &sk,
106 Rect::new(0.0, 0.0, 120.0, 16.0),
107 status,
108 &Theme::default(),
109 &mut scene,
110 );
111 match scene.primitives()[0] {
112 Primitive::Rect { color, .. } => color.a,
113 _ => panic!("expected a rect"),
114 }
115 }
116
117 #[test]
118 fn is_continuous() {
119 assert!(Widget::<()>::continuous(&Skeleton::new()));
120 }
121
122 #[test]
123 fn intensity_varies_with_time() {
124 assert_ne!(alpha_at(0.0), alpha_at(0.6));
126 }
127}