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