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(div().text_color(value_color).child(format!("{:.1}", self.value)));
236 }
237
238 container = container.child(label_row);
239 }
240
241 let on_change = self.on_change;
243 let mut track = div()
244 .id(self.id)
245 .w(px(width))
246 .h(px(thumb_size))
247 .flex()
248 .items_center()
249 .relative()
250 .child(
252 div()
253 .absolute()
254 .left_0()
255 .w_full()
256 .h(px(track_height))
257 .rounded(px(track_height / 2.0))
258 .bg(track_color),
259 )
260 .child(
262 div()
263 .absolute()
264 .left_0()
265 .w(px(fill_width))
266 .h(px(track_height))
267 .rounded(px(track_height / 2.0))
268 .bg(if disabled {
269 rgba(0xccccccff)
270 } else {
271 fill_color
272 }),
273 )
274 .child(
276 div()
277 .absolute()
278 .left(px(thumb_left.max(0.0)))
279 .w(px(thumb_size))
280 .h(px(thumb_size))
281 .rounded_full()
282 .bg(thumb_color)
283 .border_2()
284 .border_color(if disabled {
285 rgba(0xccccccff)
286 } else {
287 fill_color
288 })
289 .shadow_sm(),
290 );
291
292 if disabled {
294 track = track.cursor_not_allowed();
295 } else {
296 track = track.cursor_pointer();
297 }
298
299 if !disabled {
301 if let Some(handler) = on_change {
302 let handler_ptr: *const dyn Fn(f32, &mut Window, &mut App) = handler.as_ref();
303 track = track.on_mouse_down(MouseButton::Left, move |event, window, cx| {
304 let click_x = event.position.x;
308 let ratio = ((click_x - px(0.0)) / px(width)).clamp(0.0, 1.0);
311 let new_value = min + ratio * (max - min);
312 let snapped = if let Some(step) = step {
313 let steps = ((new_value - min) / step).round();
314 (min + steps * step).clamp(min, max)
315 } else {
316 new_value
317 };
318 unsafe { (*handler_ptr)(snapped, window, cx) };
320 });
321 std::mem::forget(handler);
322 }
323 }
324
325 container.child(track)
326 }
327}