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}
 12
 13/// A spinner indication, based on the label component, that loops through
 14/// frames of the specified animation. It implements `LabelCommon` as well.
 15///
 16/// # Default Example
 17///
 18/// ```
 19/// use ui::{SpinnerLabel};
 20///
 21/// SpinnerLabel::new();
 22/// ```
 23///
 24/// # Variant Example
 25///
 26/// ```
 27/// use ui::{SpinnerLabel};
 28///
 29/// SpinnerLabel::dots_variant();
 30/// ```
 31#[derive(IntoElement, RegisterComponent)]
 32pub struct SpinnerLabel {
 33    base: Label,
 34    variant: SpinnerVariant,
 35    frames: Vec<&'static str>,
 36    duration: Duration,
 37}
 38
 39impl SpinnerVariant {
 40    fn frames(&self) -> Vec<&'static str> {
 41        match self {
 42            SpinnerVariant::Dots => vec!["", "", "", "", "", "", "", "", "", ""],
 43            SpinnerVariant::DotsVariant => vec!["", "", "", "", "", "", "", ""],
 44        }
 45    }
 46
 47    fn duration(&self) -> Duration {
 48        match self {
 49            SpinnerVariant::Dots => Duration::from_millis(1000),
 50            SpinnerVariant::DotsVariant => Duration::from_millis(1000),
 51        }
 52    }
 53
 54    fn animation_id(&self) -> &'static str {
 55        match self {
 56            SpinnerVariant::Dots => "spinner_label_dots",
 57            SpinnerVariant::DotsVariant => "spinner_label_dots_variant",
 58        }
 59    }
 60}
 61
 62impl SpinnerLabel {
 63    pub fn new() -> Self {
 64        Self::with_variant(SpinnerVariant::default())
 65    }
 66
 67    pub fn with_variant(variant: SpinnerVariant) -> Self {
 68        let frames = variant.frames();
 69        let duration = variant.duration();
 70
 71        SpinnerLabel {
 72            base: Label::new(frames[0]),
 73            variant,
 74            frames,
 75            duration,
 76        }
 77    }
 78
 79    pub fn dots() -> Self {
 80        Self::with_variant(SpinnerVariant::Dots)
 81    }
 82
 83    pub fn dots_variant() -> Self {
 84        Self::with_variant(SpinnerVariant::DotsVariant)
 85    }
 86}
 87
 88impl LabelCommon for SpinnerLabel {
 89    fn size(mut self, size: LabelSize) -> Self {
 90        self.base = self.base.size(size);
 91        self
 92    }
 93
 94    fn weight(mut self, weight: FontWeight) -> Self {
 95        self.base = self.base.weight(weight);
 96        self
 97    }
 98
 99    fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
100        self.base = self.base.line_height_style(line_height_style);
101        self
102    }
103
104    fn color(mut self, color: Color) -> Self {
105        self.base = self.base.color(color);
106        self
107    }
108
109    fn strikethrough(mut self) -> Self {
110        self.base = self.base.strikethrough();
111        self
112    }
113
114    fn italic(mut self) -> Self {
115        self.base = self.base.italic();
116        self
117    }
118
119    fn alpha(mut self, alpha: f32) -> Self {
120        self.base = self.base.alpha(alpha);
121        self
122    }
123
124    fn underline(mut self) -> Self {
125        self.base = self.base.underline();
126        self
127    }
128
129    fn truncate(mut self) -> Self {
130        self.base = self.base.truncate();
131        self
132    }
133
134    fn single_line(mut self) -> Self {
135        self.base = self.base.single_line();
136        self
137    }
138
139    fn buffer_font(mut self, cx: &App) -> Self {
140        self.base = self.base.buffer_font(cx);
141        self
142    }
143
144    fn inline_code(mut self, cx: &App) -> Self {
145        self.base = self.base.inline_code(cx);
146        self
147    }
148}
149
150impl RenderOnce for SpinnerLabel {
151    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
152        let frames = self.frames.clone();
153        let duration = self.duration;
154
155        self.base.color(Color::Muted).with_animation(
156            self.variant.animation_id(),
157            Animation::new(duration).repeat(),
158            move |mut label, delta| {
159                let frame_index = (delta * frames.len() as f32) as usize % frames.len();
160
161                label.set_text(frames[frame_index]);
162                label
163            },
164        )
165    }
166}
167
168impl Component for SpinnerLabel {
169    fn scope() -> ComponentScope {
170        ComponentScope::Loading
171    }
172
173    fn name() -> &'static str {
174        "Spinner Label"
175    }
176
177    fn sort_name() -> &'static str {
178        "Spinner Label"
179    }
180
181    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
182        let examples = vec![
183            single_example("Default", SpinnerLabel::new().into_any_element()),
184            single_example(
185                "Dots Variant",
186                SpinnerLabel::dots_variant().into_any_element(),
187            ),
188        ];
189
190        Some(example_group(examples).vertical().into_any_element())
191    }
192}