1use jag_draw::{Brush, Color, ColorLinPremul, Rect, RoundedRadii, RoundedRect};
4use jag_surface::Canvas;
5
6use crate::event::{
7 ElementState, EventHandler, EventResult, KeyCode, KeyboardEvent, MouseButton, MouseClickEvent,
8 MouseMoveEvent, ScrollEvent,
9};
10use crate::focus::FocusId;
11
12use super::Element;
13
14pub struct Slider {
16 pub rect: Rect,
18 pub value: f32,
20 pub min: f32,
22 pub max: f32,
24 pub step: f32,
26 pub focused: bool,
28 pub dragging: bool,
30 pub track_color: ColorLinPremul,
32 pub fill_color: ColorLinPremul,
34 pub thumb_color: ColorLinPremul,
36 pub track_height: f32,
38 pub thumb_radius: f32,
40 pub focus_id: FocusId,
42}
43
44impl Slider {
45 pub fn new() -> Self {
47 Self {
48 rect: Rect {
49 x: 0.0,
50 y: 0.0,
51 w: 200.0,
52 h: 24.0,
53 },
54 value: 0.0,
55 min: 0.0,
56 max: 100.0,
57 step: 1.0,
58 focused: false,
59 dragging: false,
60 track_color: ColorLinPremul::from_srgba_u8([80, 80, 80, 255]),
61 fill_color: ColorLinPremul::from_srgba_u8([59, 130, 246, 255]),
62 thumb_color: ColorLinPremul::from_srgba_u8([255, 255, 255, 255]),
63 track_height: 4.0,
64 thumb_radius: 8.0,
65 focus_id: FocusId(0),
66 }
67 }
68
69 pub fn normalized(&self) -> f32 {
71 if (self.max - self.min).abs() < f32::EPSILON {
72 return 0.0;
73 }
74 ((self.value - self.min) / (self.max - self.min)).clamp(0.0, 1.0)
75 }
76
77 pub fn set_value(&mut self, value: f32) {
79 self.value = value.clamp(self.min, self.max);
80 }
81
82 fn thumb_x(&self) -> f32 {
84 let usable_width = self.rect.w - self.thumb_radius * 2.0;
85 self.rect.x + self.thumb_radius + usable_width * self.normalized()
86 }
87
88 fn thumb_y(&self) -> f32 {
90 self.rect.y + self.rect.h * 0.5
91 }
92
93 pub fn hit_test_thumb(&self, x: f32, y: f32) -> bool {
95 let dx = x - self.thumb_x();
96 let dy = y - self.thumb_y();
97 dx * dx + dy * dy <= self.thumb_radius * self.thumb_radius
98 }
99
100 pub fn hit_test_track(&self, x: f32, y: f32) -> bool {
102 let track_y = self.rect.y + (self.rect.h - self.track_height) * 0.5;
103 x >= self.rect.x
104 && x <= self.rect.x + self.rect.w
105 && y >= track_y
106 && y <= track_y + self.track_height
107 }
108
109 fn x_to_value(&self, x: f32) -> f32 {
111 let usable_width = self.rect.w - self.thumb_radius * 2.0;
112 if usable_width <= 0.0 {
113 return self.min;
114 }
115 let norm = ((x - self.rect.x - self.thumb_radius) / usable_width).clamp(0.0, 1.0);
116 self.min + norm * (self.max - self.min)
117 }
118}
119
120impl Default for Slider {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126impl Element for Slider {
131 fn rect(&self) -> Rect {
132 self.rect
133 }
134
135 fn set_rect(&mut self, rect: Rect) {
136 self.rect = rect;
137 }
138
139 fn render(&self, canvas: &mut Canvas, z: i32) {
140 let track_y = self.rect.y + (self.rect.h - self.track_height) * 0.5;
141 let track_r = self.track_height * 0.5;
142
143 let track_rrect = RoundedRect {
145 rect: Rect {
146 x: self.rect.x,
147 y: track_y,
148 w: self.rect.w,
149 h: self.track_height,
150 },
151 radii: RoundedRadii {
152 tl: track_r,
153 tr: track_r,
154 br: track_r,
155 bl: track_r,
156 },
157 };
158 canvas.rounded_rect(track_rrect, Brush::Solid(self.track_color), z);
159
160 let fill_width = self.thumb_x() - self.rect.x;
162 if fill_width > 0.0 {
163 let fill_rrect = RoundedRect {
164 rect: Rect {
165 x: self.rect.x,
166 y: track_y,
167 w: fill_width,
168 h: self.track_height,
169 },
170 radii: RoundedRadii {
171 tl: track_r,
172 tr: track_r,
173 br: track_r,
174 bl: track_r,
175 },
176 };
177 canvas.rounded_rect(fill_rrect, Brush::Solid(self.fill_color), z + 1);
178 }
179
180 let tx = self.thumb_x();
182 let ty = self.thumb_y();
183 canvas.ellipse(
184 [tx, ty],
185 [self.thumb_radius, self.thumb_radius],
186 Brush::Solid(self.thumb_color),
187 z + 2,
188 );
189
190 if self.focused {
192 let focus_r = self.thumb_radius + 3.0;
193 jag_surface::shapes::draw_ellipse(
194 canvas,
195 [tx, ty],
196 [focus_r, focus_r],
197 None,
198 Some(2.0),
199 Some(Brush::Solid(Color::rgba(63, 130, 246, 255))),
200 z + 3,
201 );
202 }
203 }
204
205 fn focus_id(&self) -> Option<FocusId> {
206 Some(self.focus_id)
207 }
208}
209
210impl EventHandler for Slider {
215 fn handle_mouse_click(&mut self, event: &MouseClickEvent) -> EventResult {
216 if event.button != MouseButton::Left {
217 return EventResult::Ignored;
218 }
219 match event.state {
220 ElementState::Pressed => {
221 if self.hit_test_thumb(event.x, event.y) {
222 self.dragging = true;
223 EventResult::Handled
224 } else if self.hit_test_track(event.x, event.y) {
225 self.set_value(self.x_to_value(event.x));
226 self.dragging = true;
227 EventResult::Handled
228 } else {
229 EventResult::Ignored
230 }
231 }
232 ElementState::Released => {
233 if self.dragging {
234 self.dragging = false;
235 EventResult::Handled
236 } else {
237 EventResult::Ignored
238 }
239 }
240 }
241 }
242
243 fn handle_keyboard(&mut self, event: &KeyboardEvent) -> EventResult {
244 if event.state != ElementState::Pressed || !self.focused {
245 return EventResult::Ignored;
246 }
247 match event.key {
248 KeyCode::ArrowRight | KeyCode::ArrowUp => {
249 self.set_value(self.value + self.step);
250 EventResult::Handled
251 }
252 KeyCode::ArrowLeft | KeyCode::ArrowDown => {
253 self.set_value(self.value - self.step);
254 EventResult::Handled
255 }
256 KeyCode::Home => {
257 self.set_value(self.min);
258 EventResult::Handled
259 }
260 KeyCode::End => {
261 self.set_value(self.max);
262 EventResult::Handled
263 }
264 _ => EventResult::Ignored,
265 }
266 }
267
268 fn handle_mouse_move(&mut self, event: &MouseMoveEvent) -> EventResult {
269 if self.dragging {
270 self.set_value(self.x_to_value(event.x));
271 EventResult::Handled
272 } else {
273 EventResult::Ignored
274 }
275 }
276
277 fn handle_scroll(&mut self, _event: &ScrollEvent) -> EventResult {
278 EventResult::Ignored
279 }
280
281 fn is_focused(&self) -> bool {
282 self.focused
283 }
284
285 fn set_focused(&mut self, focused: bool) {
286 self.focused = focused;
287 }
288
289 fn contains_point(&self, x: f32, y: f32) -> bool {
290 self.hit_test_thumb(x, y) || self.hit_test_track(x, y)
291 }
292}
293
294#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn slider_new_defaults() {
304 let s = Slider::new();
305 assert_eq!(s.value, 0.0);
306 assert_eq!(s.min, 0.0);
307 assert_eq!(s.max, 100.0);
308 assert!(!s.focused);
309 assert!(!s.dragging);
310 }
311
312 #[test]
313 fn slider_normalized() {
314 let mut s = Slider::new();
315 assert!((s.normalized() - 0.0).abs() < f32::EPSILON);
316 s.value = 50.0;
317 assert!((s.normalized() - 0.5).abs() < f32::EPSILON);
318 s.value = 100.0;
319 assert!((s.normalized() - 1.0).abs() < f32::EPSILON);
320 }
321
322 #[test]
323 fn slider_set_value_clamped() {
324 let mut s = Slider::new();
325 s.set_value(150.0);
326 assert_eq!(s.value, 100.0);
327 s.set_value(-10.0);
328 assert_eq!(s.value, 0.0);
329 }
330
331 #[test]
332 fn slider_keyboard_step() {
333 let mut s = Slider::new();
334 s.focused = true;
335 s.step = 10.0;
336 s.value = 50.0;
337 let right = KeyboardEvent {
338 key: KeyCode::ArrowRight,
339 state: ElementState::Pressed,
340 modifiers: Default::default(),
341 text: None,
342 };
343 assert_eq!(s.handle_keyboard(&right), EventResult::Handled);
344 assert_eq!(s.value, 60.0);
345
346 let left = KeyboardEvent {
347 key: KeyCode::ArrowLeft,
348 state: ElementState::Pressed,
349 modifiers: Default::default(),
350 text: None,
351 };
352 assert_eq!(s.handle_keyboard(&left), EventResult::Handled);
353 assert_eq!(s.value, 50.0);
354 }
355
356 #[test]
357 fn slider_focus() {
358 let mut s = Slider::new();
359 assert!(!s.is_focused());
360 s.set_focused(true);
361 assert!(s.is_focused());
362 }
363}