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