Skip to main content

ui/components/label/
spinner_label.rs

1use crate::prelude::*;
2use gpui::{Animation, AnimationExt, FontWeight};
3use std::time::Duration;
4
5/// Different types of spinner animations
6#[derive(Debug, Default, Clone, Copy, PartialEq)]
7pub enum SpinnerVariant {
8    #[default]
9    Dots,
10    DotsVariant,
11    Sand,
12}
13
14/// A spinner indication, based on the label component, that loops through
15/// frames of the specified animation. It implements `LabelCommon` as well.
16///
17/// # Default Example
18///
19/// ```
20/// use ui::{SpinnerLabel};
21///
22/// SpinnerLabel::new();
23/// ```
24///
25/// # Variant Example
26///
27/// ```
28/// use ui::{SpinnerLabel};
29///
30/// SpinnerLabel::dots_variant();
31/// ```
32#[derive(IntoElement, RegisterComponent)]
33pub struct SpinnerLabel {
34    base: Label,
35    variant: SpinnerVariant,
36    frames: Vec<&'static str>,
37    duration: Duration,
38}
39
40impl SpinnerVariant {
41    fn frames(&self) -> Vec<&'static str> {
42        match self {
43            SpinnerVariant::Dots => vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
44            SpinnerVariant::DotsVariant => vec!["⣼", "⣹", "⢻", "⠿", "⡟", "⣏", "⣧", "⣶"],
45            SpinnerVariant::Sand => vec![
46                "⠁", "⠂", "⠄", "⡀", "⡈", "⡐", "⡠", "⣀", "⣁", "⣂", "⣄", "⣌", "⣔", "⣤", "⣥", "⣦",
47                "⣮", "⣶", "⣷", "⣿", "⡿", "⠿", "⢟", "⠟", "⡛", "⠛", "⠫", "⢋", "⠋", "⠍", "⡉", "⠉",
48                "⠑", "⠡", "⢁",
49            ],
50        }
51    }
52
53    fn duration(&self) -> Duration {
54        match self {
55            SpinnerVariant::Dots => Duration::from_millis(1000),
56            SpinnerVariant::DotsVariant => Duration::from_millis(1000),
57            SpinnerVariant::Sand => Duration::from_millis(2000),
58        }
59    }
60
61    fn animation_id(&self) -> &'static str {
62        match self {
63            SpinnerVariant::Dots => "spinner_label_dots",
64            SpinnerVariant::DotsVariant => "spinner_label_dots_variant",
65            SpinnerVariant::Sand => "spinner_label_dots_variant_2",
66        }
67    }
68}
69
70impl SpinnerLabel {
71    pub fn new() -> Self {
72        Self::with_variant(SpinnerVariant::default())
73    }
74
75    pub fn with_variant(variant: SpinnerVariant) -> Self {
76        let frames = variant.frames();
77        let duration = variant.duration();
78
79        SpinnerLabel {
80            base: Label::new(frames[0]).color(Color::Muted),
81            variant,
82            frames,
83            duration,
84        }
85    }
86
87    pub fn dots() -> Self {
88        Self::with_variant(SpinnerVariant::Dots)
89    }
90
91    pub fn dots_variant() -> Self {
92        Self::with_variant(SpinnerVariant::DotsVariant)
93    }
94
95    pub fn sand() -> Self {
96        Self::with_variant(SpinnerVariant::Sand)
97    }
98}
99
100impl LabelCommon for SpinnerLabel {
101    fn size(mut self, size: LabelSize) -> Self {
102        self.base = self.base.size(size);
103        self
104    }
105
106    fn weight(mut self, weight: FontWeight) -> Self {
107        self.base = self.base.weight(weight);
108        self
109    }
110
111    fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
112        self.base = self.base.line_height_style(line_height_style);
113        self
114    }
115
116    fn color(mut self, color: Color) -> Self {
117        self.base = self.base.color(color);
118        self
119    }
120
121    fn strikethrough(mut self) -> Self {
122        self.base = self.base.strikethrough();
123        self
124    }
125
126    fn italic(mut self) -> Self {
127        self.base = self.base.italic();
128        self
129    }
130
131    fn alpha(mut self, alpha: f32) -> Self {
132        self.base = self.base.alpha(alpha);
133        self
134    }
135
136    fn underline(mut self) -> Self {
137        self.base = self.base.underline();
138        self
139    }
140
141    fn truncate(mut self) -> Self {
142        self.base = self.base.truncate();
143        self
144    }
145
146    fn single_line(mut self) -> Self {
147        self.base = self.base.single_line();
148        self
149    }
150
151    fn buffer_font(mut self, cx: &App) -> Self {
152        self.base = self.base.buffer_font(cx);
153        self
154    }
155
156    fn inline_code(mut self, cx: &App) -> Self {
157        self.base = self.base.inline_code(cx);
158        self
159    }
160}
161
162impl RenderOnce for SpinnerLabel {
163    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
164        let frames = self.frames.clone();
165        let duration = self.duration;
166
167        self.base.with_animation(
168            self.variant.animation_id(),
169            Animation::new(duration).repeat(),
170            move |mut label, delta| {
171                let frame_index = (delta * frames.len() as f32) as usize % frames.len();
172
173                label.set_text(frames[frame_index]);
174                label
175            },
176        )
177    }
178}
179
180impl Component for SpinnerLabel {
181    fn scope() -> ComponentScope {
182        ComponentScope::Loading
183    }
184
185    fn name() -> &'static str {
186        "Spinner Label"
187    }
188
189    fn sort_name() -> &'static str {
190        "Spinner Label"
191    }
192
193    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
194        let examples = vec![
195            single_example("Default", SpinnerLabel::new().into_any_element()),
196            single_example(
197                "Dots Variant",
198                SpinnerLabel::dots_variant().into_any_element(),
199            ),
200            single_example("Sand Variant", SpinnerLabel::sand().into_any_element()),
201        ];
202
203        Some(example_group(examples).vertical().into_any_element())
204    }
205}