egui_components/
slider.rs1use egui::{pos2, vec2, Color32, Response, Sense, Stroke, Ui, Widget};
9use egui_components_theme::Theme;
10
11pub struct Slider<'a> {
12 value: &'a mut f32,
13 range: std::ops::RangeInclusive<f32>,
14 width: f32,
15 disabled: bool,
16}
17
18impl<'a> Slider<'a> {
19 pub fn new(value: &'a mut f32, range: std::ops::RangeInclusive<f32>) -> Self {
20 Self { value, range, width: 200.0, disabled: false }
21 }
22 pub fn width(mut self, w: f32) -> Self {
23 self.width = w;
24 self
25 }
26 pub fn disabled(mut self, d: bool) -> Self {
27 self.disabled = d;
28 self
29 }
30}
31
32impl<'a> Widget for Slider<'a> {
33 fn ui(self, ui: &mut Ui) -> Response {
34 let theme = Theme::get(ui.ctx());
35 let m = theme.metrics;
36 let c = theme.colors;
37 let height = (m.slider_thumb_radius * 2.0).max(m.slider_track_height) + 4.0;
38 let desired = vec2(self.width, height);
39
40 let sense = if self.disabled { Sense::hover() } else { Sense::click_and_drag() };
41 let (rect, mut response) = ui.allocate_exact_size(desired, sense);
42
43 let (min, max) = (*self.range.start(), *self.range.end());
44 let track_y = rect.center().y;
45 let track_left = rect.left() + m.slider_thumb_radius;
46 let track_right = rect.right() - m.slider_thumb_radius;
47 let track_w = track_right - track_left;
48
49 if !self.disabled {
50 if let Some(pointer) = response.interact_pointer_pos() {
51 let t = ((pointer.x - track_left) / track_w).clamp(0.0, 1.0);
52 let new = min + t * (max - min);
53 if (new - *self.value).abs() > f32::EPSILON {
54 *self.value = new;
55 response.mark_changed();
56 }
57 }
58 }
59
60 if ui.is_rect_visible(rect) {
61 let t = ((*self.value - min) / (max - min)).clamp(0.0, 1.0);
62 let thumb_x = track_left + t * track_w;
63
64 let track_rect = egui::Rect::from_min_max(
65 pos2(track_left, track_y - m.slider_track_height * 0.5),
66 pos2(track_right, track_y + m.slider_track_height * 0.5),
67 );
68 let track_radius =
69 egui::CornerRadius::same((m.slider_track_height * 0.5) as u8);
70
71 let painter = ui.painter();
72
73 let track_bg = if self.disabled {
74 fade(c.muted_background)
75 } else {
76 c.muted_background
77 };
78 painter.rect_filled(track_rect, track_radius, track_bg);
79
80 let filled = egui::Rect::from_min_max(track_rect.min, pos2(thumb_x, track_rect.max.y));
82 let fill_color = if self.disabled { fade(c.slider_bar_background) } else { c.slider_bar_background };
83 painter.rect_filled(filled, track_radius, fill_color);
84
85 let thumb_color = if self.disabled { fade(c.slider_thumb_background) } else { c.slider_thumb_background };
87 let thumb_border = if self.disabled { fade(c.slider_bar_background) } else { c.slider_bar_background };
88 painter.circle(
89 pos2(thumb_x, track_y),
90 m.slider_thumb_radius,
91 thumb_color,
92 Stroke::new(2.0, thumb_border),
93 );
94
95 if response.has_focus() {
96 painter.circle_stroke(
97 pos2(thumb_x, track_y),
98 m.slider_thumb_radius + 3.0,
99 theme.focus_ring(),
100 );
101 }
102
103 if !self.disabled && response.hovered() {
104 ui.ctx().set_cursor_icon(egui::CursorIcon::Grab);
105 }
106 }
107
108 response
109 }
110}
111
112fn fade(c: Color32) -> Color32 {
113 egui_components_theme::mix(c, Color32::from_rgba_unmultiplied(0, 0, 0, 0), 0.4)
114}