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::theme::{theme, ColorName, Size};
9
10/// A pulsing placeholder block. The Mantine `Skeleton`.
11#[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    /// Render a circle of `size` (overrides width/height/radius).
45    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}