Skip to main content

guise/
skeleton.rs

1//! `Skeleton` — an animated loading placeholder.
2
3use 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/// A pulsing placeholder block.
12#[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    /// Render a circle of `size` (overrides width/height/radius).
46    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}