1use gpui::prelude::*;
6use gpui::*;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum SpinnerSize {
11 Xs,
13 Sm,
15 #[default]
17 Md,
18 Lg,
20 Xl,
22}
23
24impl SpinnerSize {
25 fn size(&self) -> Pixels {
26 match self {
27 SpinnerSize::Xs => px(12.0),
28 SpinnerSize::Sm => px(16.0),
29 SpinnerSize::Md => px(24.0),
30 SpinnerSize::Lg => px(32.0),
31 SpinnerSize::Xl => px(48.0),
32 }
33 }
34
35 fn border_width(&self) -> Pixels {
36 match self {
37 SpinnerSize::Xs => px(1.5),
38 SpinnerSize::Sm => px(2.0),
39 SpinnerSize::Md => px(2.5),
40 SpinnerSize::Lg => px(3.0),
41 SpinnerSize::Xl => px(4.0),
42 }
43 }
44}
45
46pub struct Spinner {
49 size: SpinnerSize,
50 color: Option<Rgba>,
51 label: Option<SharedString>,
52}
53
54impl Spinner {
55 pub fn new() -> Self {
57 Self {
58 size: SpinnerSize::default(),
59 color: None,
60 label: None,
61 }
62 }
63
64 pub fn size(mut self, size: SpinnerSize) -> Self {
66 self.size = size;
67 self
68 }
69
70 pub fn color(mut self, color: Rgba) -> Self {
72 self.color = Some(color);
73 self
74 }
75
76 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
78 self.label = Some(label.into());
79 self
80 }
81
82 pub fn build(self) -> Div {
84 let size = self.size.size();
85 let border_width = self.size.border_width();
86 let color = self.color.unwrap_or(rgb(0x007acc));
87
88 let mut container = div().flex().items_center().gap_2();
89
90 let spinner = div()
94 .w(size)
95 .h(size)
96 .rounded_full()
97 .border(border_width)
98 .border_color(color);
99
100 container = container.child(spinner);
101
102 if let Some(label) = self.label {
104 let label_el = match self.size {
105 SpinnerSize::Xs | SpinnerSize::Sm => div().text_xs(),
106 SpinnerSize::Md => div().text_sm(),
107 SpinnerSize::Lg | SpinnerSize::Xl => div(),
108 };
109 container = container.child(label_el.text_color(rgb(0xcccccc)).child(label));
110 }
111
112 container
113 }
114}
115
116impl Default for Spinner {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl IntoElement for Spinner {
123 type Element = Div;
124
125 fn into_element(self) -> Self::Element {
126 self.build()
127 }
128}
129
130pub struct LoadingDots {
132 size: SpinnerSize,
133 color: Option<Rgba>,
134}
135
136impl LoadingDots {
137 pub fn new() -> Self {
139 Self {
140 size: SpinnerSize::default(),
141 color: None,
142 }
143 }
144
145 pub fn size(mut self, size: SpinnerSize) -> Self {
147 self.size = size;
148 self
149 }
150
151 pub fn color(mut self, color: Rgba) -> Self {
153 self.color = Some(color);
154 self
155 }
156
157 pub fn build(self) -> Div {
159 let color = self.color.unwrap_or(rgb(0x007acc));
160 let dot_size = match self.size {
161 SpinnerSize::Xs => px(4.0),
162 SpinnerSize::Sm => px(6.0),
163 SpinnerSize::Md => px(8.0),
164 SpinnerSize::Lg => px(10.0),
165 SpinnerSize::Xl => px(12.0),
166 };
167
168 div()
169 .flex()
170 .items_center()
171 .gap_1()
172 .child(div().w(dot_size).h(dot_size).rounded_full().bg(color))
173 .child(
174 div()
175 .w(dot_size)
176 .h(dot_size)
177 .rounded_full()
178 .bg(color)
179 .opacity(0.7),
180 )
181 .child(
182 div()
183 .w(dot_size)
184 .h(dot_size)
185 .rounded_full()
186 .bg(color)
187 .opacity(0.4),
188 )
189 }
190}
191
192impl Default for LoadingDots {
193 fn default() -> Self {
194 Self::new()
195 }
196}
197
198impl IntoElement for LoadingDots {
199 type Element = Div;
200
201 fn into_element(self) -> Self::Element {
202 self.build()
203 }
204}