1use std::time::Duration;
4
5use gpui::prelude::*;
6use gpui::{div, pulsating_between, px, Animation, AnimationExt, App, IntoElement, Window};
7
8use crate::theme::{theme, ColorName, Size};
9
10#[derive(IntoElement)]
12pub struct Skeleton {
13 width: Option<f32>,
14 height: f32,
15 radius: Size,
16 circle: bool,
17}
18
19impl Skeleton {
20 pub fn new() -> Self {
21 Skeleton {
22 width: None,
23 height: 16.0,
24 radius: Size::Sm,
25 circle: false,
26 }
27 }
28
29 pub fn width(mut self, width: f32) -> Self {
30 self.width = Some(width);
31 self
32 }
33
34 pub fn height(mut self, height: f32) -> Self {
35 self.height = height;
36 self
37 }
38
39 pub fn radius(mut self, radius: Size) -> Self {
40 self.radius = radius;
41 self
42 }
43
44 pub fn circle(mut self, size: f32) -> Self {
46 self.circle = true;
47 self.width = Some(size);
48 self.height = size;
49 self
50 }
51}
52
53impl Default for Skeleton {
54 fn default() -> Self {
55 Skeleton::new()
56 }
57}
58
59impl RenderOnce for Skeleton {
60 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
61 let t = theme(cx);
62 let color = t
63 .color(ColorName::Gray, if t.scheme.is_dark() { 7 } else { 2 })
64 .hsla();
65 let radius = if self.circle {
66 self.height
67 } else {
68 t.radius(self.radius)
69 };
70
71 let mut block = div().h(px(self.height)).rounded(px(radius)).bg(color);
72 block = match self.width {
73 Some(w) => block.w(px(w)),
74 None => block.w_full(),
75 };
76
77 let pulse = pulsating_between(0.4, 1.0);
78 block.with_animation(
79 "guise-skeleton",
80 Animation::new(Duration::from_millis(1100))
81 .repeat()
82 .with_easing(pulse),
83 |block, delta| block.opacity(delta),
84 )
85 }
86}