use crate::theme::{ActiveTheme, Themeable};
use gpui::{
div, prelude::FluentBuilder, px, Animation, AnimationExt, App, Div, IntoElement, Pixels,
RenderOnce, Styled, Window,
};
use std::time::Duration;
pub fn skeleton() -> Skeleton {
Skeleton::new()
}
pub fn skeleton_text() -> Skeleton {
Skeleton::new().h(px(16.0)).w(px(200.0)).rounded(px(4.0))
}
pub fn skeleton_avatar() -> Skeleton {
Skeleton::new().size(px(40.0)).circle()
}
pub fn skeleton_card() -> Skeleton {
Skeleton::new()
.w(px(300.0))
.h(px(150.0))
.rounded(px(8.0))
}
#[derive(IntoElement)]
pub struct Skeleton {
width: Option<Pixels>,
height: Option<Pixels>,
full_width: bool,
corner_radius: Option<Pixels>,
is_circle: bool,
animated: bool,
}
impl Skeleton {
pub fn new() -> Self {
Self {
width: None,
height: None,
full_width: false,
corner_radius: Some(px(4.0)),
is_circle: false,
animated: true,
}
}
pub fn w(mut self, width: Pixels) -> Self {
self.width = Some(width);
self
}
pub fn h(mut self, height: Pixels) -> Self {
self.height = Some(height);
self
}
pub fn size(mut self, size: Pixels) -> Self {
self.width = Some(size);
self.height = Some(size);
self
}
pub fn w_full(mut self) -> Self {
self.full_width = true;
self
}
pub fn rounded(mut self, radius: Pixels) -> Self {
self.corner_radius = Some(radius);
self.is_circle = false;
self
}
pub fn circle(mut self) -> Self {
self.is_circle = true;
self.corner_radius = None;
self
}
pub fn animated(mut self, animated: bool) -> Self {
self.animated = animated;
self
}
}
impl Default for Skeleton {
fn default() -> Self {
Self::new()
}
}
impl RenderOnce for Skeleton {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let bg_color = theme.surface_secondary();
let animated = self.animated;
div()
.bg(bg_color)
.when(self.full_width, |this: Div| this.w_full())
.when_some(self.width, |this: Div, w| this.w(w))
.when_some(self.height, |this: Div, h| this.h(h))
.when(self.is_circle, |this: Div| this.rounded_full())
.when_some(self.corner_radius, |this: Div, r| this.rounded(r))
.with_animation(
"skeleton-pulse",
Animation::new(Duration::from_millis(1500)).repeat(),
move |el, delta| {
if animated {
let opacity =
0.4 + 0.6 * (1.0 - (delta * std::f32::consts::PI * 2.0).cos()) / 2.0;
el.opacity(opacity)
} else {
el
}
},
)
}
}