1use std::time::Duration;
4
5use gpui::prelude::*;
6use gpui::{div, pulsating_between, px, Animation, AnimationExt, App, IntoElement, Window};
7
8use crate::devtools::Probed;
9use crate::style::ColorValue;
10use crate::theme::{theme, ColorName, Size};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum LoaderVariant {
15 Dots,
17 Bars,
19}
20
21#[derive(IntoElement)]
23pub struct Loader {
24 variant: LoaderVariant,
25 size: Size,
26 color: ColorValue,
27}
28
29impl Loader {
30 pub fn new() -> Self {
31 Loader {
32 variant: LoaderVariant::Dots,
33 size: Size::Md,
34 color: ColorValue::Named(ColorName::Blue),
35 }
36 }
37
38 pub fn variant(mut self, variant: LoaderVariant) -> Self {
39 self.variant = variant;
40 self
41 }
42
43 pub fn size(mut self, size: Size) -> Self {
44 self.size = size;
45 self
46 }
47
48 pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
49 self.color = color.into();
50 self
51 }
52
53 fn unit(&self) -> f32 {
54 match self.size {
55 Size::Xs => 6.0,
56 Size::Sm => 8.0,
57 Size::Md => 10.0,
58 Size::Lg => 13.0,
59 Size::Xl => 16.0,
60 }
61 }
62}
63
64impl Default for Loader {
65 fn default() -> Self {
66 Loader::new()
67 }
68}
69
70impl RenderOnce for Loader {
71 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
72 let t = theme(cx);
73 let color = crate::style::solid(t, self.color);
74 let unit = self.unit();
75 let bars = self.variant == LoaderVariant::Bars;
76
77 let dots = (0..3usize).map(move |i| {
78 let phase = i as f32 / 3.0;
79 let pulse = pulsating_between(0.25, 1.0);
80 let animation = Animation::new(Duration::from_millis(900))
81 .repeat()
82 .with_easing(move |delta| pulse((delta + phase) % 1.0));
83
84 let dot = if bars {
85 div()
86 .w(px(unit * 0.6))
87 .h(px(unit * 2.4))
88 .rounded(px(unit * 0.3))
89 } else {
90 div().w(px(unit)).h(px(unit)).rounded(px(unit))
91 }
92 .bg(color);
93
94 dot.with_animation(("guise-loader-unit", i), animation, |dot, delta| {
95 dot.opacity(delta)
96 })
97 });
98
99 div()
100 .flex()
101 .items_center()
102 .gap(px(unit * 0.6))
103 .children(dots)
104 .probe("Loader")
105 }
106}