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]),
 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.color(Color::Muted).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}