1use presentar_core::{
4 widget::{AccessibleRole, LayoutResult},
5 Canvas, Color, Constraints, Event, MouseButton, Rect, Size, TypeId, Widget,
6};
7use serde::{Deserialize, Serialize};
8use std::any::Any;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct SliderChanged {
13 pub value: f32,
15}
16
17#[derive(Serialize, Deserialize)]
19pub struct Slider {
20 value: f32,
22 min: f32,
24 max: f32,
26 step: f32,
28 disabled: bool,
30 track_color: Color,
32 active_color: Color,
34 thumb_color: Color,
36 thumb_radius: f32,
38 track_height: f32,
40 test_id_value: Option<String>,
42 accessible_name_value: Option<String>,
44 #[serde(skip)]
46 bounds: Rect,
47 #[serde(skip)]
49 dragging: bool,
50}
51
52impl Default for Slider {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl Slider {
59 #[must_use]
61 pub fn new() -> Self {
62 Self {
63 value: 0.5,
64 min: 0.0,
65 max: 1.0,
66 step: 0.0,
67 disabled: false,
68 track_color: Color::new(0.8, 0.8, 0.8, 1.0),
69 active_color: Color::new(0.2, 0.6, 1.0, 1.0),
70 thumb_color: Color::WHITE,
71 thumb_radius: 10.0,
72 track_height: 4.0,
73 test_id_value: None,
74 accessible_name_value: None,
75 bounds: Rect::default(),
76 dragging: false,
77 }
78 }
79
80 #[must_use]
82 pub fn value(mut self, value: f32) -> Self {
83 self.value = value.clamp(self.min, self.max);
84 self
85 }
86
87 #[must_use]
89 pub fn min(mut self, min: f32) -> Self {
90 self.min = min;
91 if self.min <= self.max {
93 self.value = self.value.clamp(self.min, self.max);
94 }
95 self
96 }
97
98 #[must_use]
100 pub fn max(mut self, max: f32) -> Self {
101 self.max = max;
102 if self.min <= self.max {
104 self.value = self.value.clamp(self.min, self.max);
105 }
106 self
107 }
108
109 #[must_use]
111 pub fn step(mut self, step: f32) -> Self {
112 self.step = step.abs();
113 self
114 }
115
116 #[must_use]
118 pub const fn disabled(mut self, disabled: bool) -> Self {
119 self.disabled = disabled;
120 self
121 }
122
123 #[must_use]
125 pub const fn track_color(mut self, color: Color) -> Self {
126 self.track_color = color;
127 self
128 }
129
130 #[must_use]
132 pub const fn active_color(mut self, color: Color) -> Self {
133 self.active_color = color;
134 self
135 }
136
137 #[must_use]
139 pub const fn thumb_color(mut self, color: Color) -> Self {
140 self.thumb_color = color;
141 self
142 }
143
144 #[must_use]
146 pub fn thumb_radius(mut self, radius: f32) -> Self {
147 self.thumb_radius = radius.max(0.0);
148 self
149 }
150
151 #[must_use]
153 pub fn track_height(mut self, height: f32) -> Self {
154 self.track_height = height.max(0.0);
155 self
156 }
157
158 #[must_use]
160 pub fn with_test_id(mut self, id: impl Into<String>) -> Self {
161 self.test_id_value = Some(id.into());
162 self
163 }
164
165 #[must_use]
167 pub fn with_accessible_name(mut self, name: impl Into<String>) -> Self {
168 self.accessible_name_value = Some(name.into());
169 self
170 }
171
172 #[must_use]
174 pub const fn get_value(&self) -> f32 {
175 self.value
176 }
177
178 #[must_use]
180 pub const fn get_min(&self) -> f32 {
181 self.min
182 }
183
184 #[must_use]
186 pub const fn get_max(&self) -> f32 {
187 self.max
188 }
189
190 #[must_use]
192 pub fn normalized_value(&self) -> f32 {
193 if (self.max - self.min).abs() < f32::EPSILON {
194 0.0
195 } else {
196 (self.value - self.min) / (self.max - self.min)
197 }
198 }
199
200 fn set_from_normalized(&mut self, normalized: f32) {
202 let normalized = normalized.clamp(0.0, 1.0);
203 let mut new_value = self.min + normalized * (self.max - self.min);
204
205 if self.step > 0.0 {
207 new_value = (new_value / self.step).round() * self.step;
208 }
209
210 self.value = new_value.clamp(self.min, self.max);
211 }
212
213 fn thumb_x(&self) -> f32 {
215 let track_start = self.bounds.x + self.thumb_radius;
216 let track_width = 2.0f32.mul_add(-self.thumb_radius, self.bounds.width);
217 track_width.mul_add(self.normalized_value(), track_start)
218 }
219
220 fn value_from_x(&self, x: f32) -> f32 {
222 let track_start = self.bounds.x + self.thumb_radius;
223 let track_width = 2.0f32.mul_add(-self.thumb_radius, self.bounds.width);
224 if track_width <= 0.0 {
225 0.0
226 } else {
227 ((x - track_start) / track_width).clamp(0.0, 1.0)
228 }
229 }
230}
231
232impl Widget for Slider {
233 fn type_id(&self) -> TypeId {
234 TypeId::of::<Self>()
235 }
236
237 fn measure(&self, constraints: Constraints) -> Size {
238 let preferred = Size::new(200.0, self.thumb_radius * 2.0);
240 constraints.constrain(preferred)
241 }
242
243 fn layout(&mut self, bounds: Rect) -> LayoutResult {
244 self.bounds = bounds;
245 LayoutResult {
246 size: bounds.size(),
247 }
248 }
249
250 fn paint(&self, canvas: &mut dyn Canvas) {
251 let track_y = self.bounds.y + (self.bounds.height - self.track_height) / 2.0;
252 let track_rect = Rect::new(
253 self.bounds.x + self.thumb_radius,
254 track_y,
255 2.0f32.mul_add(-self.thumb_radius, self.bounds.width),
256 self.track_height,
257 );
258
259 canvas.fill_rect(track_rect, self.track_color);
261
262 let active_width = track_rect.width * self.normalized_value();
264 let active_rect = Rect::new(track_rect.x, track_rect.y, active_width, self.track_height);
265 canvas.fill_rect(active_rect, self.active_color);
266
267 let thumb_x = self.thumb_x();
269 let thumb_y = self.bounds.y + self.bounds.height / 2.0;
270 let thumb_rect = Rect::new(
271 thumb_x - self.thumb_radius,
272 thumb_y - self.thumb_radius,
273 self.thumb_radius * 2.0,
274 self.thumb_radius * 2.0,
275 );
276
277 let thumb_color = if self.disabled {
278 Color::new(0.6, 0.6, 0.6, 1.0)
279 } else {
280 self.thumb_color
281 };
282 canvas.fill_rect(thumb_rect, thumb_color);
283 }
284
285 fn event(&mut self, event: &Event) -> Option<Box<dyn Any + Send>> {
286 if self.disabled {
287 return None;
288 }
289
290 match event {
291 Event::MouseDown {
292 position,
293 button: MouseButton::Left,
294 } => {
295 if self.bounds.contains_point(position) {
297 self.dragging = true;
298 let normalized = self.value_from_x(position.x);
299 let old_value = self.value;
300 self.set_from_normalized(normalized);
301 if (self.value - old_value).abs() > f32::EPSILON {
302 return Some(Box::new(SliderChanged { value: self.value }));
303 }
304 }
305 }
306 Event::MouseUp {
307 button: MouseButton::Left,
308 ..
309 } => {
310 self.dragging = false;
311 }
312 Event::MouseMove { position } => {
313 if self.dragging {
314 let normalized = self.value_from_x(position.x);
315 let old_value = self.value;
316 self.set_from_normalized(normalized);
317 if (self.value - old_value).abs() > f32::EPSILON {
318 return Some(Box::new(SliderChanged { value: self.value }));
319 }
320 }
321 }
322 _ => {}
323 }
324
325 None
326 }
327
328 fn children(&self) -> &[Box<dyn Widget>] {
329 &[]
330 }
331
332 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
333 &mut []
334 }
335
336 fn is_interactive(&self) -> bool {
337 !self.disabled
338 }
339
340 fn is_focusable(&self) -> bool {
341 !self.disabled
342 }
343
344 fn accessible_name(&self) -> Option<&str> {
345 self.accessible_name_value.as_deref()
346 }
347
348 fn accessible_role(&self) -> AccessibleRole {
349 AccessibleRole::Slider
350 }
351
352 fn test_id(&self) -> Option<&str> {
353 self.test_id_value.as_deref()
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360 use presentar_core::Widget;
361
362 #[test]
367 fn test_slider_changed_message() {
368 let msg = SliderChanged { value: 0.75 };
369 assert_eq!(msg.value, 0.75);
370 }
371
372 #[test]
377 fn test_slider_new() {
378 let slider = Slider::new();
379 assert_eq!(slider.get_value(), 0.5);
380 assert_eq!(slider.get_min(), 0.0);
381 assert_eq!(slider.get_max(), 1.0);
382 assert!(!slider.disabled);
383 }
384
385 #[test]
386 fn test_slider_default() {
387 let slider = Slider::default();
388 assert_eq!(slider.get_value(), 0.5);
389 }
390
391 #[test]
392 fn test_slider_builder() {
393 let slider = Slider::new()
394 .value(0.3)
395 .min(0.0)
396 .max(100.0)
397 .step(10.0)
398 .disabled(true)
399 .thumb_radius(15.0)
400 .track_height(6.0)
401 .with_test_id("volume")
402 .with_accessible_name("Volume");
403
404 assert_eq!(slider.get_value(), 0.3);
405 assert_eq!(slider.get_min(), 0.0);
406 assert_eq!(slider.get_max(), 100.0);
407 assert!(slider.disabled);
408 assert_eq!(Widget::test_id(&slider), Some("volume"));
409 assert_eq!(slider.accessible_name(), Some("Volume"));
410 }
411
412 #[test]
417 fn test_slider_value_clamped() {
418 let slider = Slider::new().min(0.0).max(1.0).value(1.5);
419 assert_eq!(slider.get_value(), 1.0);
420
421 let slider = Slider::new().min(0.0).max(1.0).value(-0.5);
422 assert_eq!(slider.get_value(), 0.0);
423 }
424
425 #[test]
426 fn test_slider_normalized_value() {
427 let slider = Slider::new().min(0.0).max(100.0).value(50.0);
428 assert!((slider.normalized_value() - 0.5).abs() < f32::EPSILON);
429
430 let slider = Slider::new().min(0.0).max(100.0).value(0.0);
431 assert!((slider.normalized_value() - 0.0).abs() < f32::EPSILON);
432
433 let slider = Slider::new().min(0.0).max(100.0).value(100.0);
434 assert!((slider.normalized_value() - 1.0).abs() < f32::EPSILON);
435 }
436
437 #[test]
438 fn test_slider_normalized_value_same_min_max() {
439 let slider = Slider::new().min(50.0).max(50.0).value(50.0);
440 assert_eq!(slider.normalized_value(), 0.0);
441 }
442
443 #[test]
444 fn test_slider_step() {
445 let mut slider = Slider::new().min(0.0).max(100.0).step(10.0);
446 slider.set_from_normalized(0.45); assert!((slider.get_value() - 50.0).abs() < f32::EPSILON); }
449
450 #[test]
455 fn test_slider_type_id() {
456 let slider = Slider::new();
457 assert_eq!(Widget::type_id(&slider), TypeId::of::<Slider>());
458 }
459
460 #[test]
461 fn test_slider_measure() {
462 let slider = Slider::new();
463 let size = slider.measure(Constraints::loose(Size::new(400.0, 100.0)));
464 assert_eq!(size.width, 200.0);
465 assert_eq!(size.height, 20.0); }
467
468 #[test]
469 fn test_slider_measure_constrained() {
470 let slider = Slider::new();
471 let size = slider.measure(Constraints::tight(Size::new(100.0, 30.0)));
472 assert_eq!(size.width, 100.0);
473 assert_eq!(size.height, 30.0);
474 }
475
476 #[test]
477 fn test_slider_is_interactive() {
478 let slider = Slider::new();
479 assert!(slider.is_interactive());
480
481 let slider = Slider::new().disabled(true);
482 assert!(!slider.is_interactive());
483 }
484
485 #[test]
486 fn test_slider_is_focusable() {
487 let slider = Slider::new();
488 assert!(slider.is_focusable());
489
490 let slider = Slider::new().disabled(true);
491 assert!(!slider.is_focusable());
492 }
493
494 #[test]
495 fn test_slider_accessible_role() {
496 let slider = Slider::new();
497 assert_eq!(slider.accessible_role(), AccessibleRole::Slider);
498 }
499
500 #[test]
501 fn test_slider_children() {
502 let slider = Slider::new();
503 assert!(slider.children().is_empty());
504 }
505
506 #[test]
511 fn test_slider_colors() {
512 let slider = Slider::new()
513 .track_color(Color::RED)
514 .active_color(Color::GREEN)
515 .thumb_color(Color::BLUE);
516
517 assert_eq!(slider.track_color, Color::RED);
518 assert_eq!(slider.active_color, Color::GREEN);
519 assert_eq!(slider.thumb_color, Color::BLUE);
520 }
521
522 #[test]
527 fn test_slider_layout() {
528 let mut slider = Slider::new();
529 let bounds = Rect::new(10.0, 20.0, 200.0, 30.0);
530 let result = slider.layout(bounds);
531 assert_eq!(result.size, bounds.size());
532 assert_eq!(slider.bounds, bounds);
533 }
534
535 #[test]
540 fn test_slider_thumb_position() {
541 let mut slider = Slider::new().min(0.0).max(100.0).value(50.0);
542 slider.bounds = Rect::new(0.0, 0.0, 200.0, 20.0);
543 let thumb_x = slider.thumb_x();
546 assert!((thumb_x - 100.0).abs() < f32::EPSILON);
547 }
548
549 #[test]
550 fn test_slider_value_from_position() {
551 let mut slider = Slider::new().min(0.0).max(100.0);
552 slider.bounds = Rect::new(0.0, 0.0, 200.0, 20.0);
553 let normalized = slider.value_from_x(100.0);
555 assert!((normalized - 0.5).abs() < 0.01);
556 }
557
558 use presentar_core::draw::DrawCommand;
563 use presentar_core::RecordingCanvas;
564
565 #[test]
566 fn test_slider_paint_draws_three_rects() {
567 let mut slider = Slider::new().thumb_radius(10.0);
568 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
569
570 let mut canvas = RecordingCanvas::new();
571 slider.paint(&mut canvas);
572
573 assert_eq!(canvas.command_count(), 3);
575 }
576
577 #[test]
578 fn test_slider_paint_track_uses_track_color() {
579 let mut slider = Slider::new().track_color(Color::RED);
580 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
581
582 let mut canvas = RecordingCanvas::new();
583 slider.paint(&mut canvas);
584
585 match &canvas.commands()[0] {
587 DrawCommand::Rect { style, .. } => {
588 assert_eq!(style.fill, Some(Color::RED));
589 }
590 _ => panic!("Expected Rect command for track"),
591 }
592 }
593
594 #[test]
595 fn test_slider_paint_active_uses_active_color() {
596 let mut slider = Slider::new().active_color(Color::GREEN).value(0.5);
597 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
598
599 let mut canvas = RecordingCanvas::new();
600 slider.paint(&mut canvas);
601
602 match &canvas.commands()[1] {
604 DrawCommand::Rect { style, .. } => {
605 assert_eq!(style.fill, Some(Color::GREEN));
606 }
607 _ => panic!("Expected Rect command for active portion"),
608 }
609 }
610
611 #[test]
612 fn test_slider_paint_thumb_uses_thumb_color() {
613 let mut slider = Slider::new().thumb_color(Color::BLUE);
614 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
615
616 let mut canvas = RecordingCanvas::new();
617 slider.paint(&mut canvas);
618
619 match &canvas.commands()[2] {
621 DrawCommand::Rect { style, .. } => {
622 assert_eq!(style.fill, Some(Color::BLUE));
623 }
624 _ => panic!("Expected Rect command for thumb"),
625 }
626 }
627
628 #[test]
629 fn test_slider_paint_track_dimensions() {
630 let mut slider = Slider::new().thumb_radius(10.0).track_height(4.0);
631 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
632
633 let mut canvas = RecordingCanvas::new();
634 slider.paint(&mut canvas);
635
636 match &canvas.commands()[0] {
638 DrawCommand::Rect { bounds, .. } => {
639 assert_eq!(bounds.width, 180.0);
640 assert_eq!(bounds.height, 4.0);
641 }
642 _ => panic!("Expected Rect command for track"),
643 }
644 }
645
646 #[test]
647 fn test_slider_paint_active_width_at_50_percent() {
648 let mut slider = Slider::new().thumb_radius(10.0).value(0.5);
649 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
650
651 let mut canvas = RecordingCanvas::new();
652 slider.paint(&mut canvas);
653
654 match &canvas.commands()[1] {
656 DrawCommand::Rect { bounds, .. } => {
657 assert_eq!(bounds.width, 90.0);
658 }
659 _ => panic!("Expected Rect command for active portion"),
660 }
661 }
662
663 #[test]
664 fn test_slider_paint_active_width_at_0_percent() {
665 let mut slider = Slider::new().thumb_radius(10.0).value(0.0);
666 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
667
668 let mut canvas = RecordingCanvas::new();
669 slider.paint(&mut canvas);
670
671 match &canvas.commands()[1] {
673 DrawCommand::Rect { bounds, .. } => {
674 assert_eq!(bounds.width, 0.0);
675 }
676 _ => panic!("Expected Rect command for active portion"),
677 }
678 }
679
680 #[test]
681 fn test_slider_paint_active_width_at_100_percent() {
682 let mut slider = Slider::new().thumb_radius(10.0).value(1.0);
683 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
684
685 let mut canvas = RecordingCanvas::new();
686 slider.paint(&mut canvas);
687
688 match &canvas.commands()[1] {
690 DrawCommand::Rect { bounds, .. } => {
691 assert_eq!(bounds.width, 180.0);
692 }
693 _ => panic!("Expected Rect command for active portion"),
694 }
695 }
696
697 #[test]
698 fn test_slider_paint_thumb_size() {
699 let mut slider = Slider::new().thumb_radius(15.0);
700 slider.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
701
702 let mut canvas = RecordingCanvas::new();
703 slider.paint(&mut canvas);
704
705 match &canvas.commands()[2] {
707 DrawCommand::Rect { bounds, .. } => {
708 assert_eq!(bounds.width, 30.0);
709 assert_eq!(bounds.height, 30.0);
710 }
711 _ => panic!("Expected Rect command for thumb"),
712 }
713 }
714
715 #[test]
716 fn test_slider_paint_thumb_position_at_min() {
717 let mut slider = Slider::new().thumb_radius(10.0).value(0.0);
718 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
719
720 let mut canvas = RecordingCanvas::new();
721 slider.paint(&mut canvas);
722
723 match &canvas.commands()[2] {
726 DrawCommand::Rect { bounds, .. } => {
727 assert_eq!(bounds.x, 0.0);
728 }
729 _ => panic!("Expected Rect command for thumb"),
730 }
731 }
732
733 #[test]
734 fn test_slider_paint_thumb_position_at_max() {
735 let mut slider = Slider::new().thumb_radius(10.0).value(1.0);
736 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
737
738 let mut canvas = RecordingCanvas::new();
739 slider.paint(&mut canvas);
740
741 match &canvas.commands()[2] {
744 DrawCommand::Rect { bounds, .. } => {
745 assert_eq!(bounds.x, 180.0);
746 }
747 _ => panic!("Expected Rect command for thumb"),
748 }
749 }
750
751 #[test]
752 fn test_slider_paint_thumb_position_at_50_percent() {
753 let mut slider = Slider::new().thumb_radius(10.0).value(0.5);
754 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
755
756 let mut canvas = RecordingCanvas::new();
757 slider.paint(&mut canvas);
758
759 match &canvas.commands()[2] {
762 DrawCommand::Rect { bounds, .. } => {
763 assert_eq!(bounds.x, 90.0);
764 }
765 _ => panic!("Expected Rect command for thumb"),
766 }
767 }
768
769 #[test]
770 fn test_slider_paint_track_centered_vertically() {
771 let mut slider = Slider::new().track_height(4.0);
772 slider.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
773
774 let mut canvas = RecordingCanvas::new();
775 slider.paint(&mut canvas);
776
777 match &canvas.commands()[0] {
779 DrawCommand::Rect { bounds, .. } => {
780 assert_eq!(bounds.y, 13.0);
781 }
782 _ => panic!("Expected Rect command for track"),
783 }
784 }
785
786 #[test]
787 fn test_slider_paint_thumb_centered_vertically() {
788 let mut slider = Slider::new().thumb_radius(10.0);
789 slider.layout(Rect::new(0.0, 0.0, 200.0, 40.0));
790
791 let mut canvas = RecordingCanvas::new();
792 slider.paint(&mut canvas);
793
794 match &canvas.commands()[2] {
796 DrawCommand::Rect { bounds, .. } => {
797 assert_eq!(bounds.y, 10.0);
798 }
799 _ => panic!("Expected Rect command for thumb"),
800 }
801 }
802
803 #[test]
804 fn test_slider_paint_position_from_layout() {
805 let mut slider = Slider::new().thumb_radius(10.0);
806 slider.layout(Rect::new(50.0, 100.0, 200.0, 20.0));
807
808 let mut canvas = RecordingCanvas::new();
809 slider.paint(&mut canvas);
810
811 match &canvas.commands()[0] {
813 DrawCommand::Rect { bounds, .. } => {
814 assert_eq!(bounds.x, 60.0);
815 }
816 _ => panic!("Expected Rect command for track"),
817 }
818 }
819
820 #[test]
821 fn test_slider_paint_disabled_thumb_color() {
822 let mut slider = Slider::new().thumb_color(Color::WHITE).disabled(true);
823 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
824
825 let mut canvas = RecordingCanvas::new();
826 slider.paint(&mut canvas);
827
828 match &canvas.commands()[2] {
830 DrawCommand::Rect { style, .. } => {
831 let fill = style.fill.unwrap();
832 assert!((fill.r - 0.6).abs() < 0.01);
833 assert!((fill.g - 0.6).abs() < 0.01);
834 assert!((fill.b - 0.6).abs() < 0.01);
835 }
836 _ => panic!("Expected Rect command for thumb"),
837 }
838 }
839
840 #[test]
841 fn test_slider_paint_with_range() {
842 let mut slider = Slider::new()
843 .min(0.0)
844 .max(100.0)
845 .value(25.0)
846 .thumb_radius(10.0);
847 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
848
849 let mut canvas = RecordingCanvas::new();
850 slider.paint(&mut canvas);
851
852 match &canvas.commands()[1] {
854 DrawCommand::Rect { bounds, .. } => {
855 assert_eq!(bounds.width, 45.0);
856 }
857 _ => panic!("Expected Rect command for active portion"),
858 }
859 }
860
861 use presentar_core::Point;
866
867 #[test]
868 fn test_slider_event_mouse_down_starts_drag() {
869 let mut slider = Slider::new().thumb_radius(10.0);
870 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
871
872 assert!(!slider.dragging);
873 slider.event(&Event::MouseDown {
874 position: Point::new(100.0, 10.0),
875 button: MouseButton::Left,
876 });
877 assert!(slider.dragging);
878 }
879
880 #[test]
881 fn test_slider_event_mouse_down_updates_value() {
882 let mut slider = Slider::new()
883 .min(0.0)
884 .max(1.0)
885 .value(0.0)
886 .thumb_radius(10.0);
887 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
888
889 let result = slider.event(&Event::MouseDown {
892 position: Point::new(100.0, 10.0),
893 button: MouseButton::Left,
894 });
895
896 assert!((slider.get_value() - 0.5).abs() < 0.01);
897 assert!(result.is_some()); }
899
900 #[test]
901 fn test_slider_event_mouse_down_emits_slider_changed() {
902 let mut slider = Slider::new()
903 .min(0.0)
904 .max(100.0)
905 .value(0.0)
906 .thumb_radius(10.0);
907 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
908
909 let result = slider.event(&Event::MouseDown {
910 position: Point::new(100.0, 10.0),
911 button: MouseButton::Left,
912 });
913
914 let msg = result.unwrap().downcast::<SliderChanged>().unwrap();
915 assert!((msg.value - 50.0).abs() < 1.0); }
917
918 #[test]
919 fn test_slider_event_mouse_down_outside_bounds_no_drag() {
920 let mut slider = Slider::new().thumb_radius(10.0);
921 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
922
923 let result = slider.event(&Event::MouseDown {
924 position: Point::new(300.0, 10.0), button: MouseButton::Left,
926 });
927
928 assert!(!slider.dragging);
929 assert!(result.is_none());
930 }
931
932 #[test]
933 fn test_slider_event_mouse_down_right_button_no_drag() {
934 let mut slider = Slider::new().thumb_radius(10.0);
935 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
936
937 let result = slider.event(&Event::MouseDown {
938 position: Point::new(100.0, 10.0),
939 button: MouseButton::Right,
940 });
941
942 assert!(!slider.dragging);
943 assert!(result.is_none());
944 }
945
946 #[test]
947 fn test_slider_event_mouse_up_ends_drag() {
948 let mut slider = Slider::new().thumb_radius(10.0);
949 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
950
951 slider.event(&Event::MouseDown {
953 position: Point::new(100.0, 10.0),
954 button: MouseButton::Left,
955 });
956 assert!(slider.dragging);
957
958 let result = slider.event(&Event::MouseUp {
960 position: Point::new(100.0, 10.0),
961 button: MouseButton::Left,
962 });
963 assert!(!slider.dragging);
964 assert!(result.is_none()); }
966
967 #[test]
968 fn test_slider_event_mouse_up_right_button_no_effect() {
969 let mut slider = Slider::new().thumb_radius(10.0);
970 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
971
972 slider.event(&Event::MouseDown {
974 position: Point::new(100.0, 10.0),
975 button: MouseButton::Left,
976 });
977 assert!(slider.dragging);
978
979 slider.event(&Event::MouseUp {
981 position: Point::new(100.0, 10.0),
982 button: MouseButton::Right,
983 });
984 assert!(slider.dragging); }
986
987 #[test]
988 fn test_slider_event_mouse_move_during_drag_updates_value() {
989 let mut slider = Slider::new()
990 .min(0.0)
991 .max(1.0)
992 .value(0.0)
993 .thumb_radius(10.0);
994 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
995
996 slider.event(&Event::MouseDown {
998 position: Point::new(10.0, 10.0),
999 button: MouseButton::Left,
1000 });
1001
1002 let result = slider.event(&Event::MouseMove {
1004 position: Point::new(100.0, 10.0),
1005 });
1006
1007 assert!((slider.get_value() - 0.5).abs() < 0.01);
1008 assert!(result.is_some());
1009 }
1010
1011 #[test]
1012 fn test_slider_event_mouse_move_without_drag_no_effect() {
1013 let mut slider = Slider::new()
1014 .min(0.0)
1015 .max(1.0)
1016 .value(0.5)
1017 .thumb_radius(10.0);
1018 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1019
1020 let result = slider.event(&Event::MouseMove {
1021 position: Point::new(190.0, 10.0),
1022 });
1023
1024 assert_eq!(slider.get_value(), 0.5); assert!(result.is_none());
1026 }
1027
1028 #[test]
1029 fn test_slider_event_drag_to_minimum() {
1030 let mut slider = Slider::new()
1031 .min(0.0)
1032 .max(100.0)
1033 .value(50.0)
1034 .thumb_radius(10.0);
1035 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1036
1037 slider.event(&Event::MouseDown {
1039 position: Point::new(100.0, 10.0),
1040 button: MouseButton::Left,
1041 });
1042
1043 slider.event(&Event::MouseMove {
1045 position: Point::new(-50.0, 10.0),
1046 });
1047
1048 assert_eq!(slider.get_value(), 0.0);
1049 }
1050
1051 #[test]
1052 fn test_slider_event_drag_to_maximum() {
1053 let mut slider = Slider::new()
1054 .min(0.0)
1055 .max(100.0)
1056 .value(50.0)
1057 .thumb_radius(10.0);
1058 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1059
1060 slider.event(&Event::MouseDown {
1062 position: Point::new(100.0, 10.0),
1063 button: MouseButton::Left,
1064 });
1065
1066 slider.event(&Event::MouseMove {
1068 position: Point::new(300.0, 10.0),
1069 });
1070
1071 assert_eq!(slider.get_value(), 100.0);
1072 }
1073
1074 #[test]
1075 fn test_slider_event_drag_with_step() {
1076 let mut slider = Slider::new()
1077 .min(0.0)
1078 .max(100.0)
1079 .value(0.0)
1080 .step(25.0)
1081 .thumb_radius(10.0);
1082 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1083
1084 slider.event(&Event::MouseDown {
1086 position: Point::new(10.0, 10.0),
1087 button: MouseButton::Left,
1088 });
1089
1090 slider.event(&Event::MouseMove {
1092 position: Point::new(64.0, 10.0), });
1094
1095 assert_eq!(slider.get_value(), 25.0);
1096 }
1097
1098 #[test]
1099 fn test_slider_event_disabled_blocks_mouse_down() {
1100 let mut slider = Slider::new().value(0.5).disabled(true).thumb_radius(10.0);
1101 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1102
1103 let result = slider.event(&Event::MouseDown {
1104 position: Point::new(100.0, 10.0),
1105 button: MouseButton::Left,
1106 });
1107
1108 assert!(!slider.dragging);
1109 assert_eq!(slider.get_value(), 0.5); assert!(result.is_none());
1111 }
1112
1113 #[test]
1114 fn test_slider_event_disabled_blocks_mouse_move() {
1115 let mut slider = Slider::new().value(0.5).disabled(true).thumb_radius(10.0);
1116 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1117 slider.dragging = true; let result = slider.event(&Event::MouseMove {
1120 position: Point::new(190.0, 10.0),
1121 });
1122
1123 assert_eq!(slider.get_value(), 0.5); assert!(result.is_none());
1125 }
1126
1127 #[test]
1128 fn test_slider_event_no_message_when_value_unchanged() {
1129 let mut slider = Slider::new()
1130 .min(0.0)
1131 .max(1.0)
1132 .value(0.5)
1133 .thumb_radius(10.0);
1134 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1135
1136 let result = slider.event(&Event::MouseDown {
1138 position: Point::new(100.0, 10.0), button: MouseButton::Left,
1140 });
1141
1142 assert!(result.is_none());
1144 }
1145
1146 #[test]
1147 fn test_slider_event_full_drag_flow() {
1148 let mut slider = Slider::new()
1149 .min(0.0)
1150 .max(100.0)
1151 .value(0.0)
1152 .thumb_radius(10.0);
1153 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1154
1155 let result1 = slider.event(&Event::MouseDown {
1157 position: Point::new(10.0, 10.0),
1158 button: MouseButton::Left,
1159 });
1160 assert!(slider.dragging);
1161 assert!(result1.is_none()); let result2 = slider.event(&Event::MouseMove {
1165 position: Point::new(55.0, 10.0),
1166 });
1167 assert!((slider.get_value() - 25.0).abs() < 1.0);
1168 assert!(result2.is_some());
1169
1170 let result3 = slider.event(&Event::MouseMove {
1172 position: Point::new(145.0, 10.0),
1173 });
1174 assert!((slider.get_value() - 75.0).abs() < 1.0);
1175 assert!(result3.is_some());
1176
1177 let result4 = slider.event(&Event::MouseUp {
1179 position: Point::new(145.0, 10.0),
1180 button: MouseButton::Left,
1181 });
1182 assert!(!slider.dragging);
1183 assert!(result4.is_none());
1184
1185 let result5 = slider.event(&Event::MouseMove {
1187 position: Point::new(10.0, 10.0),
1188 });
1189 assert!((slider.get_value() - 75.0).abs() < 1.0); assert!(result5.is_none());
1191 }
1192
1193 #[test]
1194 fn test_slider_event_bounds_with_offset() {
1195 let mut slider = Slider::new()
1196 .min(0.0)
1197 .max(100.0)
1198 .value(0.0)
1199 .thumb_radius(10.0);
1200 slider.layout(Rect::new(50.0, 100.0, 200.0, 20.0));
1201
1202 slider.event(&Event::MouseDown {
1205 position: Point::new(150.0, 110.0),
1206 button: MouseButton::Left,
1207 });
1208
1209 assert!((slider.get_value() - 50.0).abs() < 1.0);
1210 }
1211}