1use presentar_core::{
4 widget::{AccessibleRole, Brick, BrickAssertion, BrickBudget, BrickVerification, LayoutResult},
5 Canvas, Color, Constraints, Event, MouseButton, Rect, Size, TypeId, Widget,
6};
7use serde::{Deserialize, Serialize};
8use std::any::Any;
9use std::time::Duration;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct SliderChanged {
14 pub value: f32,
16}
17
18#[derive(Serialize, Deserialize)]
20pub struct Slider {
21 value: f32,
23 min: f32,
25 max: f32,
27 step: f32,
29 disabled: bool,
31 track_color: Color,
33 active_color: Color,
35 thumb_color: Color,
37 thumb_radius: f32,
39 track_height: f32,
41 test_id_value: Option<String>,
43 accessible_name_value: Option<String>,
45 #[serde(skip)]
47 bounds: Rect,
48 #[serde(skip)]
50 dragging: bool,
51}
52
53impl Default for Slider {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl Slider {
60 #[must_use]
62 pub fn new() -> Self {
63 Self {
64 value: 0.5,
65 min: 0.0,
66 max: 1.0,
67 step: 0.0,
68 disabled: false,
69 track_color: Color::new(0.8, 0.8, 0.8, 1.0),
70 active_color: Color::new(0.2, 0.6, 1.0, 1.0),
71 thumb_color: Color::WHITE,
72 thumb_radius: 10.0,
73 track_height: 4.0,
74 test_id_value: None,
75 accessible_name_value: None,
76 bounds: Rect::default(),
77 dragging: false,
78 }
79 }
80
81 #[must_use]
83 pub fn value(mut self, value: f32) -> Self {
84 self.value = value.clamp(self.min, self.max);
85 self
86 }
87
88 #[must_use]
90 pub fn min(mut self, min: f32) -> Self {
91 self.min = min;
92 if self.min <= self.max {
94 self.value = self.value.clamp(self.min, self.max);
95 }
96 self
97 }
98
99 #[must_use]
101 pub fn max(mut self, max: f32) -> Self {
102 self.max = max;
103 if self.min <= self.max {
105 self.value = self.value.clamp(self.min, self.max);
106 }
107 self
108 }
109
110 #[must_use]
112 pub fn step(mut self, step: f32) -> Self {
113 self.step = step.abs();
114 self
115 }
116
117 #[must_use]
119 pub const fn disabled(mut self, disabled: bool) -> Self {
120 self.disabled = disabled;
121 self
122 }
123
124 #[must_use]
126 pub const fn track_color(mut self, color: Color) -> Self {
127 self.track_color = color;
128 self
129 }
130
131 #[must_use]
133 pub const fn active_color(mut self, color: Color) -> Self {
134 self.active_color = color;
135 self
136 }
137
138 #[must_use]
140 pub const fn thumb_color(mut self, color: Color) -> Self {
141 self.thumb_color = color;
142 self
143 }
144
145 #[must_use]
147 pub fn thumb_radius(mut self, radius: f32) -> Self {
148 self.thumb_radius = radius.max(0.0);
149 self
150 }
151
152 #[must_use]
154 pub fn track_height(mut self, height: f32) -> Self {
155 self.track_height = height.max(0.0);
156 self
157 }
158
159 #[must_use]
161 pub fn with_test_id(mut self, id: impl Into<String>) -> Self {
162 self.test_id_value = Some(id.into());
163 self
164 }
165
166 #[must_use]
168 pub fn with_accessible_name(mut self, name: impl Into<String>) -> Self {
169 self.accessible_name_value = Some(name.into());
170 self
171 }
172
173 #[must_use]
175 pub const fn get_value(&self) -> f32 {
176 self.value
177 }
178
179 #[must_use]
181 pub const fn get_min(&self) -> f32 {
182 self.min
183 }
184
185 #[must_use]
187 pub const fn get_max(&self) -> f32 {
188 self.max
189 }
190
191 #[must_use]
193 pub fn normalized_value(&self) -> f32 {
194 if (self.max - self.min).abs() < f32::EPSILON {
195 0.0
196 } else {
197 (self.value - self.min) / (self.max - self.min)
198 }
199 }
200
201 fn set_from_normalized(&mut self, normalized: f32) {
203 let normalized = normalized.clamp(0.0, 1.0);
204 let mut new_value = self.min + normalized * (self.max - self.min);
205
206 if self.step > 0.0 {
208 new_value = (new_value / self.step).round() * self.step;
209 }
210
211 self.value = new_value.clamp(self.min, self.max);
212 }
213
214 fn thumb_x(&self) -> f32 {
216 let track_start = self.bounds.x + self.thumb_radius;
217 let track_width = 2.0f32.mul_add(-self.thumb_radius, self.bounds.width);
218 track_width.mul_add(self.normalized_value(), track_start)
219 }
220
221 fn value_from_x(&self, x: f32) -> f32 {
223 let track_start = self.bounds.x + self.thumb_radius;
224 let track_width = 2.0f32.mul_add(-self.thumb_radius, self.bounds.width);
225 if track_width <= 0.0 {
226 0.0
227 } else {
228 ((x - track_start) / track_width).clamp(0.0, 1.0)
229 }
230 }
231}
232
233impl Widget for Slider {
234 fn type_id(&self) -> TypeId {
235 TypeId::of::<Self>()
236 }
237
238 fn measure(&self, constraints: Constraints) -> Size {
239 let preferred = Size::new(200.0, self.thumb_radius * 2.0);
241 constraints.constrain(preferred)
242 }
243
244 fn layout(&mut self, bounds: Rect) -> LayoutResult {
245 self.bounds = bounds;
246 LayoutResult {
247 size: bounds.size(),
248 }
249 }
250
251 fn paint(&self, canvas: &mut dyn Canvas) {
252 let track_y = self.bounds.y + (self.bounds.height - self.track_height) / 2.0;
253 let track_rect = Rect::new(
254 self.bounds.x + self.thumb_radius,
255 track_y,
256 2.0f32.mul_add(-self.thumb_radius, self.bounds.width),
257 self.track_height,
258 );
259
260 canvas.fill_rect(track_rect, self.track_color);
262
263 let active_width = track_rect.width * self.normalized_value();
265 let active_rect = Rect::new(track_rect.x, track_rect.y, active_width, self.track_height);
266 canvas.fill_rect(active_rect, self.active_color);
267
268 let thumb_x = self.thumb_x();
270 let thumb_y = self.bounds.y + self.bounds.height / 2.0;
271 let thumb_rect = Rect::new(
272 thumb_x - self.thumb_radius,
273 thumb_y - self.thumb_radius,
274 self.thumb_radius * 2.0,
275 self.thumb_radius * 2.0,
276 );
277
278 let thumb_color = if self.disabled {
279 Color::new(0.6, 0.6, 0.6, 1.0)
280 } else {
281 self.thumb_color
282 };
283 canvas.fill_rect(thumb_rect, thumb_color);
284 }
285
286 fn event(&mut self, event: &Event) -> Option<Box<dyn Any + Send>> {
287 if self.disabled {
288 return None;
289 }
290
291 match event {
292 Event::MouseDown {
293 position,
294 button: MouseButton::Left,
295 } => {
296 if self.bounds.contains_point(position) {
298 self.dragging = true;
299 let normalized = self.value_from_x(position.x);
300 let old_value = self.value;
301 self.set_from_normalized(normalized);
302 if (self.value - old_value).abs() > f32::EPSILON {
303 return Some(Box::new(SliderChanged { value: self.value }));
304 }
305 }
306 }
307 Event::MouseUp {
308 button: MouseButton::Left,
309 ..
310 } => {
311 self.dragging = false;
312 }
313 Event::MouseMove { position } 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 None
325 }
326
327 fn children(&self) -> &[Box<dyn Widget>] {
328 &[]
329 }
330
331 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
332 &mut []
333 }
334
335 fn is_interactive(&self) -> bool {
336 !self.disabled
337 }
338
339 fn is_focusable(&self) -> bool {
340 !self.disabled
341 }
342
343 fn accessible_name(&self) -> Option<&str> {
344 self.accessible_name_value.as_deref()
345 }
346
347 fn accessible_role(&self) -> AccessibleRole {
348 AccessibleRole::Slider
349 }
350
351 fn test_id(&self) -> Option<&str> {
352 self.test_id_value.as_deref()
353 }
354}
355
356impl Brick for Slider {
358 fn brick_name(&self) -> &'static str {
359 "Slider"
360 }
361
362 fn assertions(&self) -> &[BrickAssertion] {
363 &[BrickAssertion::MaxLatencyMs(16)]
364 }
365
366 fn budget(&self) -> BrickBudget {
367 BrickBudget::uniform(16)
368 }
369
370 fn verify(&self) -> BrickVerification {
371 BrickVerification {
372 passed: self.assertions().to_vec(),
373 failed: vec![],
374 verification_time: Duration::from_micros(10),
375 }
376 }
377
378 fn to_html(&self) -> String {
379 r#"<div class="brick-slider"></div>"#.to_string()
380 }
381
382 fn to_css(&self) -> String {
383 ".brick-slider { display: block; }".to_string()
384 }
385}
386
387#[cfg(test)]
388#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
389mod tests {
390 use super::*;
391 use presentar_core::Widget;
392
393 #[test]
398 fn test_slider_changed_message() {
399 let msg = SliderChanged { value: 0.75 };
400 assert_eq!(msg.value, 0.75);
401 }
402
403 #[test]
408 fn test_slider_new() {
409 let slider = Slider::new();
410 assert_eq!(slider.get_value(), 0.5);
411 assert_eq!(slider.get_min(), 0.0);
412 assert_eq!(slider.get_max(), 1.0);
413 assert!(!slider.disabled);
414 }
415
416 #[test]
417 fn test_slider_default() {
418 let slider = Slider::default();
419 assert_eq!(slider.get_value(), 0.5);
420 }
421
422 #[test]
423 fn test_slider_builder() {
424 let slider = Slider::new()
425 .value(0.3)
426 .min(0.0)
427 .max(100.0)
428 .step(10.0)
429 .disabled(true)
430 .thumb_radius(15.0)
431 .track_height(6.0)
432 .with_test_id("volume")
433 .with_accessible_name("Volume");
434
435 assert_eq!(slider.get_value(), 0.3);
436 assert_eq!(slider.get_min(), 0.0);
437 assert_eq!(slider.get_max(), 100.0);
438 assert!(slider.disabled);
439 assert_eq!(Widget::test_id(&slider), Some("volume"));
440 assert_eq!(slider.accessible_name(), Some("Volume"));
441 }
442
443 #[test]
448 fn test_slider_value_clamped() {
449 let slider = Slider::new().min(0.0).max(1.0).value(1.5);
450 assert_eq!(slider.get_value(), 1.0);
451
452 let slider = Slider::new().min(0.0).max(1.0).value(-0.5);
453 assert_eq!(slider.get_value(), 0.0);
454 }
455
456 #[test]
457 fn test_slider_normalized_value() {
458 let slider = Slider::new().min(0.0).max(100.0).value(50.0);
459 assert!((slider.normalized_value() - 0.5).abs() < f32::EPSILON);
460
461 let slider = Slider::new().min(0.0).max(100.0).value(0.0);
462 assert!((slider.normalized_value() - 0.0).abs() < f32::EPSILON);
463
464 let slider = Slider::new().min(0.0).max(100.0).value(100.0);
465 assert!((slider.normalized_value() - 1.0).abs() < f32::EPSILON);
466 }
467
468 #[test]
469 fn test_slider_normalized_value_same_min_max() {
470 let slider = Slider::new().min(50.0).max(50.0).value(50.0);
471 assert_eq!(slider.normalized_value(), 0.0);
472 }
473
474 #[test]
475 fn test_slider_step() {
476 let mut slider = Slider::new().min(0.0).max(100.0).step(10.0);
477 slider.set_from_normalized(0.45); assert!((slider.get_value() - 50.0).abs() < f32::EPSILON); }
480
481 #[test]
486 fn test_slider_type_id() {
487 let slider = Slider::new();
488 assert_eq!(Widget::type_id(&slider), TypeId::of::<Slider>());
489 }
490
491 #[test]
492 fn test_slider_measure() {
493 let slider = Slider::new();
494 let size = slider.measure(Constraints::loose(Size::new(400.0, 100.0)));
495 assert_eq!(size.width, 200.0);
496 assert_eq!(size.height, 20.0); }
498
499 #[test]
500 fn test_slider_measure_constrained() {
501 let slider = Slider::new();
502 let size = slider.measure(Constraints::tight(Size::new(100.0, 30.0)));
503 assert_eq!(size.width, 100.0);
504 assert_eq!(size.height, 30.0);
505 }
506
507 #[test]
508 fn test_slider_is_interactive() {
509 let slider = Slider::new();
510 assert!(slider.is_interactive());
511
512 let slider = Slider::new().disabled(true);
513 assert!(!slider.is_interactive());
514 }
515
516 #[test]
517 fn test_slider_is_focusable() {
518 let slider = Slider::new();
519 assert!(slider.is_focusable());
520
521 let slider = Slider::new().disabled(true);
522 assert!(!slider.is_focusable());
523 }
524
525 #[test]
526 fn test_slider_accessible_role() {
527 let slider = Slider::new();
528 assert_eq!(slider.accessible_role(), AccessibleRole::Slider);
529 }
530
531 #[test]
532 fn test_slider_children() {
533 let slider = Slider::new();
534 assert!(slider.children().is_empty());
535 }
536
537 #[test]
542 fn test_slider_colors() {
543 let slider = Slider::new()
544 .track_color(Color::RED)
545 .active_color(Color::GREEN)
546 .thumb_color(Color::BLUE);
547
548 assert_eq!(slider.track_color, Color::RED);
549 assert_eq!(slider.active_color, Color::GREEN);
550 assert_eq!(slider.thumb_color, Color::BLUE);
551 }
552
553 #[test]
558 fn test_slider_layout() {
559 let mut slider = Slider::new();
560 let bounds = Rect::new(10.0, 20.0, 200.0, 30.0);
561 let result = slider.layout(bounds);
562 assert_eq!(result.size, bounds.size());
563 assert_eq!(slider.bounds, bounds);
564 }
565
566 #[test]
571 fn test_slider_thumb_position() {
572 let mut slider = Slider::new().min(0.0).max(100.0).value(50.0);
573 slider.bounds = Rect::new(0.0, 0.0, 200.0, 20.0);
574 let thumb_x = slider.thumb_x();
577 assert!((thumb_x - 100.0).abs() < f32::EPSILON);
578 }
579
580 #[test]
581 fn test_slider_value_from_position() {
582 let mut slider = Slider::new().min(0.0).max(100.0);
583 slider.bounds = Rect::new(0.0, 0.0, 200.0, 20.0);
584 let normalized = slider.value_from_x(100.0);
586 assert!((normalized - 0.5).abs() < 0.01);
587 }
588
589 use presentar_core::draw::DrawCommand;
594 use presentar_core::RecordingCanvas;
595
596 #[test]
597 fn test_slider_paint_draws_three_rects() {
598 let mut slider = Slider::new().thumb_radius(10.0);
599 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
600
601 let mut canvas = RecordingCanvas::new();
602 slider.paint(&mut canvas);
603
604 assert_eq!(canvas.command_count(), 3);
606 }
607
608 #[test]
609 fn test_slider_paint_track_uses_track_color() {
610 let mut slider = Slider::new().track_color(Color::RED);
611 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
612
613 let mut canvas = RecordingCanvas::new();
614 slider.paint(&mut canvas);
615
616 match &canvas.commands()[0] {
618 DrawCommand::Rect { style, .. } => {
619 assert_eq!(style.fill, Some(Color::RED));
620 }
621 _ => panic!("Expected Rect command for track"),
622 }
623 }
624
625 #[test]
626 fn test_slider_paint_active_uses_active_color() {
627 let mut slider = Slider::new().active_color(Color::GREEN).value(0.5);
628 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
629
630 let mut canvas = RecordingCanvas::new();
631 slider.paint(&mut canvas);
632
633 match &canvas.commands()[1] {
635 DrawCommand::Rect { style, .. } => {
636 assert_eq!(style.fill, Some(Color::GREEN));
637 }
638 _ => panic!("Expected Rect command for active portion"),
639 }
640 }
641
642 #[test]
643 fn test_slider_paint_thumb_uses_thumb_color() {
644 let mut slider = Slider::new().thumb_color(Color::BLUE);
645 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
646
647 let mut canvas = RecordingCanvas::new();
648 slider.paint(&mut canvas);
649
650 match &canvas.commands()[2] {
652 DrawCommand::Rect { style, .. } => {
653 assert_eq!(style.fill, Some(Color::BLUE));
654 }
655 _ => panic!("Expected Rect command for thumb"),
656 }
657 }
658
659 #[test]
660 fn test_slider_paint_track_dimensions() {
661 let mut slider = Slider::new().thumb_radius(10.0).track_height(4.0);
662 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
663
664 let mut canvas = RecordingCanvas::new();
665 slider.paint(&mut canvas);
666
667 match &canvas.commands()[0] {
669 DrawCommand::Rect { bounds, .. } => {
670 assert_eq!(bounds.width, 180.0);
671 assert_eq!(bounds.height, 4.0);
672 }
673 _ => panic!("Expected Rect command for track"),
674 }
675 }
676
677 #[test]
678 fn test_slider_paint_active_width_at_50_percent() {
679 let mut slider = Slider::new().thumb_radius(10.0).value(0.5);
680 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
681
682 let mut canvas = RecordingCanvas::new();
683 slider.paint(&mut canvas);
684
685 match &canvas.commands()[1] {
687 DrawCommand::Rect { bounds, .. } => {
688 assert_eq!(bounds.width, 90.0);
689 }
690 _ => panic!("Expected Rect command for active portion"),
691 }
692 }
693
694 #[test]
695 fn test_slider_paint_active_width_at_0_percent() {
696 let mut slider = Slider::new().thumb_radius(10.0).value(0.0);
697 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
698
699 let mut canvas = RecordingCanvas::new();
700 slider.paint(&mut canvas);
701
702 match &canvas.commands()[1] {
704 DrawCommand::Rect { bounds, .. } => {
705 assert_eq!(bounds.width, 0.0);
706 }
707 _ => panic!("Expected Rect command for active portion"),
708 }
709 }
710
711 #[test]
712 fn test_slider_paint_active_width_at_100_percent() {
713 let mut slider = Slider::new().thumb_radius(10.0).value(1.0);
714 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
715
716 let mut canvas = RecordingCanvas::new();
717 slider.paint(&mut canvas);
718
719 match &canvas.commands()[1] {
721 DrawCommand::Rect { bounds, .. } => {
722 assert_eq!(bounds.width, 180.0);
723 }
724 _ => panic!("Expected Rect command for active portion"),
725 }
726 }
727
728 #[test]
729 fn test_slider_paint_thumb_size() {
730 let mut slider = Slider::new().thumb_radius(15.0);
731 slider.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
732
733 let mut canvas = RecordingCanvas::new();
734 slider.paint(&mut canvas);
735
736 match &canvas.commands()[2] {
738 DrawCommand::Rect { bounds, .. } => {
739 assert_eq!(bounds.width, 30.0);
740 assert_eq!(bounds.height, 30.0);
741 }
742 _ => panic!("Expected Rect command for thumb"),
743 }
744 }
745
746 #[test]
747 fn test_slider_paint_thumb_position_at_min() {
748 let mut slider = Slider::new().thumb_radius(10.0).value(0.0);
749 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
750
751 let mut canvas = RecordingCanvas::new();
752 slider.paint(&mut canvas);
753
754 match &canvas.commands()[2] {
757 DrawCommand::Rect { bounds, .. } => {
758 assert_eq!(bounds.x, 0.0);
759 }
760 _ => panic!("Expected Rect command for thumb"),
761 }
762 }
763
764 #[test]
765 fn test_slider_paint_thumb_position_at_max() {
766 let mut slider = Slider::new().thumb_radius(10.0).value(1.0);
767 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
768
769 let mut canvas = RecordingCanvas::new();
770 slider.paint(&mut canvas);
771
772 match &canvas.commands()[2] {
775 DrawCommand::Rect { bounds, .. } => {
776 assert_eq!(bounds.x, 180.0);
777 }
778 _ => panic!("Expected Rect command for thumb"),
779 }
780 }
781
782 #[test]
783 fn test_slider_paint_thumb_position_at_50_percent() {
784 let mut slider = Slider::new().thumb_radius(10.0).value(0.5);
785 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
786
787 let mut canvas = RecordingCanvas::new();
788 slider.paint(&mut canvas);
789
790 match &canvas.commands()[2] {
793 DrawCommand::Rect { bounds, .. } => {
794 assert_eq!(bounds.x, 90.0);
795 }
796 _ => panic!("Expected Rect command for thumb"),
797 }
798 }
799
800 #[test]
801 fn test_slider_paint_track_centered_vertically() {
802 let mut slider = Slider::new().track_height(4.0);
803 slider.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
804
805 let mut canvas = RecordingCanvas::new();
806 slider.paint(&mut canvas);
807
808 match &canvas.commands()[0] {
810 DrawCommand::Rect { bounds, .. } => {
811 assert_eq!(bounds.y, 13.0);
812 }
813 _ => panic!("Expected Rect command for track"),
814 }
815 }
816
817 #[test]
818 fn test_slider_paint_thumb_centered_vertically() {
819 let mut slider = Slider::new().thumb_radius(10.0);
820 slider.layout(Rect::new(0.0, 0.0, 200.0, 40.0));
821
822 let mut canvas = RecordingCanvas::new();
823 slider.paint(&mut canvas);
824
825 match &canvas.commands()[2] {
827 DrawCommand::Rect { bounds, .. } => {
828 assert_eq!(bounds.y, 10.0);
829 }
830 _ => panic!("Expected Rect command for thumb"),
831 }
832 }
833
834 #[test]
835 fn test_slider_paint_position_from_layout() {
836 let mut slider = Slider::new().thumb_radius(10.0);
837 slider.layout(Rect::new(50.0, 100.0, 200.0, 20.0));
838
839 let mut canvas = RecordingCanvas::new();
840 slider.paint(&mut canvas);
841
842 match &canvas.commands()[0] {
844 DrawCommand::Rect { bounds, .. } => {
845 assert_eq!(bounds.x, 60.0);
846 }
847 _ => panic!("Expected Rect command for track"),
848 }
849 }
850
851 #[test]
852 fn test_slider_paint_disabled_thumb_color() {
853 let mut slider = Slider::new().thumb_color(Color::WHITE).disabled(true);
854 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
855
856 let mut canvas = RecordingCanvas::new();
857 slider.paint(&mut canvas);
858
859 match &canvas.commands()[2] {
861 DrawCommand::Rect { style, .. } => {
862 let fill = style.fill.unwrap();
863 assert!((fill.r - 0.6).abs() < 0.01);
864 assert!((fill.g - 0.6).abs() < 0.01);
865 assert!((fill.b - 0.6).abs() < 0.01);
866 }
867 _ => panic!("Expected Rect command for thumb"),
868 }
869 }
870
871 #[test]
872 fn test_slider_paint_with_range() {
873 let mut slider = Slider::new()
874 .min(0.0)
875 .max(100.0)
876 .value(25.0)
877 .thumb_radius(10.0);
878 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
879
880 let mut canvas = RecordingCanvas::new();
881 slider.paint(&mut canvas);
882
883 match &canvas.commands()[1] {
885 DrawCommand::Rect { bounds, .. } => {
886 assert_eq!(bounds.width, 45.0);
887 }
888 _ => panic!("Expected Rect command for active portion"),
889 }
890 }
891
892 use presentar_core::Point;
897
898 #[test]
899 fn test_slider_event_mouse_down_starts_drag() {
900 let mut slider = Slider::new().thumb_radius(10.0);
901 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
902
903 assert!(!slider.dragging);
904 slider.event(&Event::MouseDown {
905 position: Point::new(100.0, 10.0),
906 button: MouseButton::Left,
907 });
908 assert!(slider.dragging);
909 }
910
911 #[test]
912 fn test_slider_event_mouse_down_updates_value() {
913 let mut slider = Slider::new()
914 .min(0.0)
915 .max(1.0)
916 .value(0.0)
917 .thumb_radius(10.0);
918 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
919
920 let result = slider.event(&Event::MouseDown {
923 position: Point::new(100.0, 10.0),
924 button: MouseButton::Left,
925 });
926
927 assert!((slider.get_value() - 0.5).abs() < 0.01);
928 assert!(result.is_some()); }
930
931 #[test]
932 fn test_slider_event_mouse_down_emits_slider_changed() {
933 let mut slider = Slider::new()
934 .min(0.0)
935 .max(100.0)
936 .value(0.0)
937 .thumb_radius(10.0);
938 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
939
940 let result = slider.event(&Event::MouseDown {
941 position: Point::new(100.0, 10.0),
942 button: MouseButton::Left,
943 });
944
945 let msg = result.unwrap().downcast::<SliderChanged>().unwrap();
946 assert!((msg.value - 50.0).abs() < 1.0); }
948
949 #[test]
950 fn test_slider_event_mouse_down_outside_bounds_no_drag() {
951 let mut slider = Slider::new().thumb_radius(10.0);
952 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
953
954 let result = slider.event(&Event::MouseDown {
955 position: Point::new(300.0, 10.0), button: MouseButton::Left,
957 });
958
959 assert!(!slider.dragging);
960 assert!(result.is_none());
961 }
962
963 #[test]
964 fn test_slider_event_mouse_down_right_button_no_drag() {
965 let mut slider = Slider::new().thumb_radius(10.0);
966 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
967
968 let result = slider.event(&Event::MouseDown {
969 position: Point::new(100.0, 10.0),
970 button: MouseButton::Right,
971 });
972
973 assert!(!slider.dragging);
974 assert!(result.is_none());
975 }
976
977 #[test]
978 fn test_slider_event_mouse_up_ends_drag() {
979 let mut slider = Slider::new().thumb_radius(10.0);
980 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
981
982 slider.event(&Event::MouseDown {
984 position: Point::new(100.0, 10.0),
985 button: MouseButton::Left,
986 });
987 assert!(slider.dragging);
988
989 let result = slider.event(&Event::MouseUp {
991 position: Point::new(100.0, 10.0),
992 button: MouseButton::Left,
993 });
994 assert!(!slider.dragging);
995 assert!(result.is_none()); }
997
998 #[test]
999 fn test_slider_event_mouse_up_right_button_no_effect() {
1000 let mut slider = Slider::new().thumb_radius(10.0);
1001 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1002
1003 slider.event(&Event::MouseDown {
1005 position: Point::new(100.0, 10.0),
1006 button: MouseButton::Left,
1007 });
1008 assert!(slider.dragging);
1009
1010 slider.event(&Event::MouseUp {
1012 position: Point::new(100.0, 10.0),
1013 button: MouseButton::Right,
1014 });
1015 assert!(slider.dragging); }
1017
1018 #[test]
1019 fn test_slider_event_mouse_move_during_drag_updates_value() {
1020 let mut slider = Slider::new()
1021 .min(0.0)
1022 .max(1.0)
1023 .value(0.0)
1024 .thumb_radius(10.0);
1025 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1026
1027 slider.event(&Event::MouseDown {
1029 position: Point::new(10.0, 10.0),
1030 button: MouseButton::Left,
1031 });
1032
1033 let result = slider.event(&Event::MouseMove {
1035 position: Point::new(100.0, 10.0),
1036 });
1037
1038 assert!((slider.get_value() - 0.5).abs() < 0.01);
1039 assert!(result.is_some());
1040 }
1041
1042 #[test]
1043 fn test_slider_event_mouse_move_without_drag_no_effect() {
1044 let mut slider = Slider::new()
1045 .min(0.0)
1046 .max(1.0)
1047 .value(0.5)
1048 .thumb_radius(10.0);
1049 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1050
1051 let result = slider.event(&Event::MouseMove {
1052 position: Point::new(190.0, 10.0),
1053 });
1054
1055 assert_eq!(slider.get_value(), 0.5); assert!(result.is_none());
1057 }
1058
1059 #[test]
1060 fn test_slider_event_drag_to_minimum() {
1061 let mut slider = Slider::new()
1062 .min(0.0)
1063 .max(100.0)
1064 .value(50.0)
1065 .thumb_radius(10.0);
1066 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1067
1068 slider.event(&Event::MouseDown {
1070 position: Point::new(100.0, 10.0),
1071 button: MouseButton::Left,
1072 });
1073
1074 slider.event(&Event::MouseMove {
1076 position: Point::new(-50.0, 10.0),
1077 });
1078
1079 assert_eq!(slider.get_value(), 0.0);
1080 }
1081
1082 #[test]
1083 fn test_slider_event_drag_to_maximum() {
1084 let mut slider = Slider::new()
1085 .min(0.0)
1086 .max(100.0)
1087 .value(50.0)
1088 .thumb_radius(10.0);
1089 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1090
1091 slider.event(&Event::MouseDown {
1093 position: Point::new(100.0, 10.0),
1094 button: MouseButton::Left,
1095 });
1096
1097 slider.event(&Event::MouseMove {
1099 position: Point::new(300.0, 10.0),
1100 });
1101
1102 assert_eq!(slider.get_value(), 100.0);
1103 }
1104
1105 #[test]
1106 fn test_slider_event_drag_with_step() {
1107 let mut slider = Slider::new()
1108 .min(0.0)
1109 .max(100.0)
1110 .value(0.0)
1111 .step(25.0)
1112 .thumb_radius(10.0);
1113 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1114
1115 slider.event(&Event::MouseDown {
1117 position: Point::new(10.0, 10.0),
1118 button: MouseButton::Left,
1119 });
1120
1121 slider.event(&Event::MouseMove {
1123 position: Point::new(64.0, 10.0), });
1125
1126 assert_eq!(slider.get_value(), 25.0);
1127 }
1128
1129 #[test]
1130 fn test_slider_event_disabled_blocks_mouse_down() {
1131 let mut slider = Slider::new().value(0.5).disabled(true).thumb_radius(10.0);
1132 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1133
1134 let result = slider.event(&Event::MouseDown {
1135 position: Point::new(100.0, 10.0),
1136 button: MouseButton::Left,
1137 });
1138
1139 assert!(!slider.dragging);
1140 assert_eq!(slider.get_value(), 0.5); assert!(result.is_none());
1142 }
1143
1144 #[test]
1145 fn test_slider_event_disabled_blocks_mouse_move() {
1146 let mut slider = Slider::new().value(0.5).disabled(true).thumb_radius(10.0);
1147 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1148 slider.dragging = true; let result = slider.event(&Event::MouseMove {
1151 position: Point::new(190.0, 10.0),
1152 });
1153
1154 assert_eq!(slider.get_value(), 0.5); assert!(result.is_none());
1156 }
1157
1158 #[test]
1159 fn test_slider_event_no_message_when_value_unchanged() {
1160 let mut slider = Slider::new()
1161 .min(0.0)
1162 .max(1.0)
1163 .value(0.5)
1164 .thumb_radius(10.0);
1165 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1166
1167 let result = slider.event(&Event::MouseDown {
1169 position: Point::new(100.0, 10.0), button: MouseButton::Left,
1171 });
1172
1173 assert!(result.is_none());
1175 }
1176
1177 #[test]
1178 fn test_slider_event_full_drag_flow() {
1179 let mut slider = Slider::new()
1180 .min(0.0)
1181 .max(100.0)
1182 .value(0.0)
1183 .thumb_radius(10.0);
1184 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1185
1186 let result1 = slider.event(&Event::MouseDown {
1188 position: Point::new(10.0, 10.0),
1189 button: MouseButton::Left,
1190 });
1191 assert!(slider.dragging);
1192 assert!(result1.is_none()); let result2 = slider.event(&Event::MouseMove {
1196 position: Point::new(55.0, 10.0),
1197 });
1198 assert!((slider.get_value() - 25.0).abs() < 1.0);
1199 assert!(result2.is_some());
1200
1201 let result3 = slider.event(&Event::MouseMove {
1203 position: Point::new(145.0, 10.0),
1204 });
1205 assert!((slider.get_value() - 75.0).abs() < 1.0);
1206 assert!(result3.is_some());
1207
1208 let result4 = slider.event(&Event::MouseUp {
1210 position: Point::new(145.0, 10.0),
1211 button: MouseButton::Left,
1212 });
1213 assert!(!slider.dragging);
1214 assert!(result4.is_none());
1215
1216 let result5 = slider.event(&Event::MouseMove {
1218 position: Point::new(10.0, 10.0),
1219 });
1220 assert!((slider.get_value() - 75.0).abs() < 1.0); assert!(result5.is_none());
1222 }
1223
1224 #[test]
1225 fn test_slider_event_bounds_with_offset() {
1226 let mut slider = Slider::new()
1227 .min(0.0)
1228 .max(100.0)
1229 .value(0.0)
1230 .thumb_radius(10.0);
1231 slider.layout(Rect::new(50.0, 100.0, 200.0, 20.0));
1232
1233 slider.event(&Event::MouseDown {
1236 position: Point::new(150.0, 110.0),
1237 button: MouseButton::Left,
1238 });
1239
1240 assert!((slider.get_value() - 50.0).abs() < 1.0);
1241 }
1242}