1use gpui::*;
4
5#[derive(Debug, Clone)]
7pub struct SliderTheme {
8 pub track: Rgba,
10 pub fill: Rgba,
12 pub thumb: Rgba,
14 pub label: Rgba,
16 pub value: Rgba,
18}
19
20impl Default for SliderTheme {
21 fn default() -> Self {
22 Self {
23 track: rgba(0x3e3e3eff),
24 fill: rgba(0x007accff),
25 thumb: rgba(0xffffffff),
26 label: rgba(0xccccccff),
27 value: rgba(0x999999ff),
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
34pub enum SliderSize {
35 Small,
36 #[default]
37 Medium,
38 Large,
39}
40
41impl SliderSize {
42 fn track_height(&self) -> f32 {
43 match self {
44 Self::Small => 4.0,
45 Self::Medium => 6.0,
46 Self::Large => 8.0,
47 }
48 }
49
50 fn thumb_size(&self) -> f32 {
51 match self {
52 Self::Small => 14.0,
53 Self::Medium => 18.0,
54 Self::Large => 22.0,
55 }
56 }
57}
58
59#[derive(IntoElement)]
61pub struct Slider {
62 id: ElementId,
63 value: f32,
64 min: f32,
65 max: f32,
66 step: Option<f32>,
67 size: SliderSize,
68 disabled: bool,
69 show_value: bool,
70 label: Option<SharedString>,
71 width: f32,
72 on_change: Option<Box<dyn Fn(f32, &mut Window, &mut App) + 'static>>,
73 track_color: Option<Rgba>,
74 fill_color: Option<Rgba>,
75 thumb_color: Option<Rgba>,
76 theme: Option<SliderTheme>,
77}
78
79impl Slider {
80 pub fn new(id: impl Into<ElementId>) -> Self {
82 Self {
83 id: id.into(),
84 value: 0.0,
85 min: 0.0,
86 max: 100.0,
87 step: None,
88 size: SliderSize::default(),
89 disabled: false,
90 show_value: false,
91 label: None,
92 width: 200.0,
93 on_change: None,
94 track_color: None,
95 fill_color: None,
96 thumb_color: None,
97 theme: None,
98 }
99 }
100
101 pub fn value(mut self, value: f32) -> Self {
103 self.value = value.clamp(self.min, self.max);
104 self
105 }
106
107 pub fn min(mut self, min: f32) -> Self {
109 self.min = min;
110 self
111 }
112
113 pub fn max(mut self, max: f32) -> Self {
115 self.max = max;
116 self
117 }
118
119 pub fn step(mut self, step: f32) -> Self {
121 self.step = Some(step);
122 self
123 }
124
125 pub fn size(mut self, size: SliderSize) -> Self {
127 self.size = size;
128 self
129 }
130
131 pub fn disabled(mut self, disabled: bool) -> Self {
133 self.disabled = disabled;
134 self
135 }
136
137 pub fn show_value(mut self, show: bool) -> Self {
139 self.show_value = show;
140 self
141 }
142
143 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
145 self.label = Some(label.into());
146 self
147 }
148
149 pub fn width(mut self, width: f32) -> Self {
151 self.width = width;
152 self
153 }
154
155 pub fn on_change(mut self, handler: impl Fn(f32, &mut Window, &mut App) + 'static) -> Self {
157 self.on_change = Some(Box::new(handler));
158 self
159 }
160
161 pub fn track_color(mut self, color: impl Into<Rgba>) -> Self {
163 self.track_color = Some(color.into());
164 self
165 }
166
167 pub fn fill_color(mut self, color: impl Into<Rgba>) -> Self {
169 self.fill_color = Some(color.into());
170 self
171 }
172
173 pub fn thumb_color(mut self, color: impl Into<Rgba>) -> Self {
175 self.thumb_color = Some(color.into());
176 self
177 }
178
179 pub fn theme(mut self, theme: SliderTheme) -> Self {
181 self.theme = Some(theme);
182 self
183 }
184}
185
186impl RenderOnce for Slider {
187 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
188 let track_height = self.size.track_height();
189 let thumb_size = self.size.thumb_size();
190 let width = self.width;
191
192 let default_theme = SliderTheme::default();
194 let theme = self.theme.as_ref().unwrap_or(&default_theme);
195 let track_color = self.track_color.unwrap_or(theme.track);
196 let fill_color = self.fill_color.unwrap_or(theme.fill);
197 let thumb_color = self.thumb_color.unwrap_or(theme.thumb);
198 let label_color = theme.label;
199 let value_color = theme.value;
200
201 let range = self.max - self.min;
202 let progress = if range > 0.0 {
203 (self.value - self.min) / range
204 } else {
205 0.0
206 };
207
208 let fill_width = (width * progress).max(0.0);
209 let thumb_left = (width * progress) - (thumb_size / 2.0);
210
211 let min = self.min;
212 let max = self.max;
213 let step = self.step;
214 let disabled = self.disabled;
215
216 let mut container = div().flex().flex_col().gap_1();
217
218 if self.label.is_some() || self.show_value {
220 let mut label_row = div().flex().justify_between().w(px(width)).text_sm();
221
222 if let Some(label) = &self.label {
223 label_row = label_row.child(
224 div()
225 .text_color(if disabled {
226 rgba(0x66666699)
227 } else {
228 label_color
229 })
230 .child(label.clone()),
231 );
232 }
233
234 if self.show_value {
235 label_row = label_row.child(
236 div()
237 .text_color(value_color)
238 .child(format!("{:.1}", self.value)),
239 );
240 }
241
242 container = container.child(label_row);
243 }
244
245 let on_change = self.on_change;
247 let mut track = div()
248 .id(self.id)
249 .w(px(width))
250 .h(px(thumb_size))
251 .flex()
252 .items_center()
253 .relative()
254 .child(
256 div()
257 .absolute()
258 .left_0()
259 .w_full()
260 .h(px(track_height))
261 .rounded(px(track_height / 2.0))
262 .bg(track_color),
263 )
264 .child(
266 div()
267 .absolute()
268 .left_0()
269 .w(px(fill_width))
270 .h(px(track_height))
271 .rounded(px(track_height / 2.0))
272 .bg(if disabled {
273 rgba(0xccccccff)
274 } else {
275 fill_color
276 }),
277 )
278 .child(
280 div()
281 .absolute()
282 .left(px(thumb_left.max(0.0)))
283 .w(px(thumb_size))
284 .h(px(thumb_size))
285 .rounded_full()
286 .bg(thumb_color)
287 .border_2()
288 .border_color(if disabled {
289 rgba(0xccccccff)
290 } else {
291 fill_color
292 })
293 .shadow_sm(),
294 );
295
296 if disabled {
298 track = track.cursor_not_allowed();
299 } else {
300 track = track.cursor_pointer();
301 }
302
303 if !disabled {
306 if let Some(handler) = on_change {
307 let handler = std::rc::Rc::new(handler);
308
309 let current_value = self.value;
311
312 track = track.on_mouse_down(MouseButton::Left, move |_event, window, cx| {
313 let step_amount = step.unwrap_or((max - min) / 10.0);
318
319 let new_value = current_value + step_amount;
321 let snapped = if new_value > max {
322 min } else if let Some(step) = step {
324 let steps = ((new_value - min) / step).round();
325 (min + steps * step).clamp(min, max)
326 } else {
327 new_value.clamp(min, max)
328 };
329 handler(snapped, window, cx);
330 });
331 }
332 }
333
334 container.child(track)
335 }
336}