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 } => {
314 if self.dragging {
315 let normalized = self.value_from_x(position.x);
316 let old_value = self.value;
317 self.set_from_normalized(normalized);
318 if (self.value - old_value).abs() > f32::EPSILON {
319 return Some(Box::new(SliderChanged { value: self.value }));
320 }
321 }
322 }
323 _ => {}
324 }
325
326 None
327 }
328
329 fn children(&self) -> &[Box<dyn Widget>] {
330 &[]
331 }
332
333 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
334 &mut []
335 }
336
337 fn is_interactive(&self) -> bool {
338 !self.disabled
339 }
340
341 fn is_focusable(&self) -> bool {
342 !self.disabled
343 }
344
345 fn accessible_name(&self) -> Option<&str> {
346 self.accessible_name_value.as_deref()
347 }
348
349 fn accessible_role(&self) -> AccessibleRole {
350 AccessibleRole::Slider
351 }
352
353 fn test_id(&self) -> Option<&str> {
354 self.test_id_value.as_deref()
355 }
356}
357
358impl Brick for Slider {
360 fn brick_name(&self) -> &'static str {
361 "Slider"
362 }
363
364 fn assertions(&self) -> &[BrickAssertion] {
365 &[BrickAssertion::MaxLatencyMs(16)]
366 }
367
368 fn budget(&self) -> BrickBudget {
369 BrickBudget::uniform(16)
370 }
371
372 fn verify(&self) -> BrickVerification {
373 BrickVerification {
374 passed: self.assertions().to_vec(),
375 failed: vec![],
376 verification_time: Duration::from_micros(10),
377 }
378 }
379
380 fn to_html(&self) -> String {
381 r#"<div class="brick-slider"></div>"#.to_string()
382 }
383
384 fn to_css(&self) -> String {
385 ".brick-slider { display: block; }".to_string()
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use presentar_core::Widget;
393
394 #[test]
399 fn test_slider_changed_message() {
400 let msg = SliderChanged { value: 0.75 };
401 assert_eq!(msg.value, 0.75);
402 }
403
404 #[test]
409 fn test_slider_new() {
410 let slider = Slider::new();
411 assert_eq!(slider.get_value(), 0.5);
412 assert_eq!(slider.get_min(), 0.0);
413 assert_eq!(slider.get_max(), 1.0);
414 assert!(!slider.disabled);
415 }
416
417 #[test]
418 fn test_slider_default() {
419 let slider = Slider::default();
420 assert_eq!(slider.get_value(), 0.5);
421 }
422
423 #[test]
424 fn test_slider_builder() {
425 let slider = Slider::new()
426 .value(0.3)
427 .min(0.0)
428 .max(100.0)
429 .step(10.0)
430 .disabled(true)
431 .thumb_radius(15.0)
432 .track_height(6.0)
433 .with_test_id("volume")
434 .with_accessible_name("Volume");
435
436 assert_eq!(slider.get_value(), 0.3);
437 assert_eq!(slider.get_min(), 0.0);
438 assert_eq!(slider.get_max(), 100.0);
439 assert!(slider.disabled);
440 assert_eq!(Widget::test_id(&slider), Some("volume"));
441 assert_eq!(slider.accessible_name(), Some("Volume"));
442 }
443
444 #[test]
449 fn test_slider_value_clamped() {
450 let slider = Slider::new().min(0.0).max(1.0).value(1.5);
451 assert_eq!(slider.get_value(), 1.0);
452
453 let slider = Slider::new().min(0.0).max(1.0).value(-0.5);
454 assert_eq!(slider.get_value(), 0.0);
455 }
456
457 #[test]
458 fn test_slider_normalized_value() {
459 let slider = Slider::new().min(0.0).max(100.0).value(50.0);
460 assert!((slider.normalized_value() - 0.5).abs() < f32::EPSILON);
461
462 let slider = Slider::new().min(0.0).max(100.0).value(0.0);
463 assert!((slider.normalized_value() - 0.0).abs() < f32::EPSILON);
464
465 let slider = Slider::new().min(0.0).max(100.0).value(100.0);
466 assert!((slider.normalized_value() - 1.0).abs() < f32::EPSILON);
467 }
468
469 #[test]
470 fn test_slider_normalized_value_same_min_max() {
471 let slider = Slider::new().min(50.0).max(50.0).value(50.0);
472 assert_eq!(slider.normalized_value(), 0.0);
473 }
474
475 #[test]
476 fn test_slider_step() {
477 let mut slider = Slider::new().min(0.0).max(100.0).step(10.0);
478 slider.set_from_normalized(0.45); assert!((slider.get_value() - 50.0).abs() < f32::EPSILON); }
481
482 #[test]
487 fn test_slider_type_id() {
488 let slider = Slider::new();
489 assert_eq!(Widget::type_id(&slider), TypeId::of::<Slider>());
490 }
491
492 #[test]
493 fn test_slider_measure() {
494 let slider = Slider::new();
495 let size = slider.measure(Constraints::loose(Size::new(400.0, 100.0)));
496 assert_eq!(size.width, 200.0);
497 assert_eq!(size.height, 20.0); }
499
500 #[test]
501 fn test_slider_measure_constrained() {
502 let slider = Slider::new();
503 let size = slider.measure(Constraints::tight(Size::new(100.0, 30.0)));
504 assert_eq!(size.width, 100.0);
505 assert_eq!(size.height, 30.0);
506 }
507
508 #[test]
509 fn test_slider_is_interactive() {
510 let slider = Slider::new();
511 assert!(slider.is_interactive());
512
513 let slider = Slider::new().disabled(true);
514 assert!(!slider.is_interactive());
515 }
516
517 #[test]
518 fn test_slider_is_focusable() {
519 let slider = Slider::new();
520 assert!(slider.is_focusable());
521
522 let slider = Slider::new().disabled(true);
523 assert!(!slider.is_focusable());
524 }
525
526 #[test]
527 fn test_slider_accessible_role() {
528 let slider = Slider::new();
529 assert_eq!(slider.accessible_role(), AccessibleRole::Slider);
530 }
531
532 #[test]
533 fn test_slider_children() {
534 let slider = Slider::new();
535 assert!(slider.children().is_empty());
536 }
537
538 #[test]
543 fn test_slider_colors() {
544 let slider = Slider::new()
545 .track_color(Color::RED)
546 .active_color(Color::GREEN)
547 .thumb_color(Color::BLUE);
548
549 assert_eq!(slider.track_color, Color::RED);
550 assert_eq!(slider.active_color, Color::GREEN);
551 assert_eq!(slider.thumb_color, Color::BLUE);
552 }
553
554 #[test]
559 fn test_slider_layout() {
560 let mut slider = Slider::new();
561 let bounds = Rect::new(10.0, 20.0, 200.0, 30.0);
562 let result = slider.layout(bounds);
563 assert_eq!(result.size, bounds.size());
564 assert_eq!(slider.bounds, bounds);
565 }
566
567 #[test]
572 fn test_slider_thumb_position() {
573 let mut slider = Slider::new().min(0.0).max(100.0).value(50.0);
574 slider.bounds = Rect::new(0.0, 0.0, 200.0, 20.0);
575 let thumb_x = slider.thumb_x();
578 assert!((thumb_x - 100.0).abs() < f32::EPSILON);
579 }
580
581 #[test]
582 fn test_slider_value_from_position() {
583 let mut slider = Slider::new().min(0.0).max(100.0);
584 slider.bounds = Rect::new(0.0, 0.0, 200.0, 20.0);
585 let normalized = slider.value_from_x(100.0);
587 assert!((normalized - 0.5).abs() < 0.01);
588 }
589
590 use presentar_core::draw::DrawCommand;
595 use presentar_core::RecordingCanvas;
596
597 #[test]
598 fn test_slider_paint_draws_three_rects() {
599 let mut slider = Slider::new().thumb_radius(10.0);
600 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
601
602 let mut canvas = RecordingCanvas::new();
603 slider.paint(&mut canvas);
604
605 assert_eq!(canvas.command_count(), 3);
607 }
608
609 #[test]
610 fn test_slider_paint_track_uses_track_color() {
611 let mut slider = Slider::new().track_color(Color::RED);
612 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
613
614 let mut canvas = RecordingCanvas::new();
615 slider.paint(&mut canvas);
616
617 match &canvas.commands()[0] {
619 DrawCommand::Rect { style, .. } => {
620 assert_eq!(style.fill, Some(Color::RED));
621 }
622 _ => panic!("Expected Rect command for track"),
623 }
624 }
625
626 #[test]
627 fn test_slider_paint_active_uses_active_color() {
628 let mut slider = Slider::new().active_color(Color::GREEN).value(0.5);
629 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
630
631 let mut canvas = RecordingCanvas::new();
632 slider.paint(&mut canvas);
633
634 match &canvas.commands()[1] {
636 DrawCommand::Rect { style, .. } => {
637 assert_eq!(style.fill, Some(Color::GREEN));
638 }
639 _ => panic!("Expected Rect command for active portion"),
640 }
641 }
642
643 #[test]
644 fn test_slider_paint_thumb_uses_thumb_color() {
645 let mut slider = Slider::new().thumb_color(Color::BLUE);
646 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
647
648 let mut canvas = RecordingCanvas::new();
649 slider.paint(&mut canvas);
650
651 match &canvas.commands()[2] {
653 DrawCommand::Rect { style, .. } => {
654 assert_eq!(style.fill, Some(Color::BLUE));
655 }
656 _ => panic!("Expected Rect command for thumb"),
657 }
658 }
659
660 #[test]
661 fn test_slider_paint_track_dimensions() {
662 let mut slider = Slider::new().thumb_radius(10.0).track_height(4.0);
663 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
664
665 let mut canvas = RecordingCanvas::new();
666 slider.paint(&mut canvas);
667
668 match &canvas.commands()[0] {
670 DrawCommand::Rect { bounds, .. } => {
671 assert_eq!(bounds.width, 180.0);
672 assert_eq!(bounds.height, 4.0);
673 }
674 _ => panic!("Expected Rect command for track"),
675 }
676 }
677
678 #[test]
679 fn test_slider_paint_active_width_at_50_percent() {
680 let mut slider = Slider::new().thumb_radius(10.0).value(0.5);
681 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
682
683 let mut canvas = RecordingCanvas::new();
684 slider.paint(&mut canvas);
685
686 match &canvas.commands()[1] {
688 DrawCommand::Rect { bounds, .. } => {
689 assert_eq!(bounds.width, 90.0);
690 }
691 _ => panic!("Expected Rect command for active portion"),
692 }
693 }
694
695 #[test]
696 fn test_slider_paint_active_width_at_0_percent() {
697 let mut slider = Slider::new().thumb_radius(10.0).value(0.0);
698 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
699
700 let mut canvas = RecordingCanvas::new();
701 slider.paint(&mut canvas);
702
703 match &canvas.commands()[1] {
705 DrawCommand::Rect { bounds, .. } => {
706 assert_eq!(bounds.width, 0.0);
707 }
708 _ => panic!("Expected Rect command for active portion"),
709 }
710 }
711
712 #[test]
713 fn test_slider_paint_active_width_at_100_percent() {
714 let mut slider = Slider::new().thumb_radius(10.0).value(1.0);
715 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
716
717 let mut canvas = RecordingCanvas::new();
718 slider.paint(&mut canvas);
719
720 match &canvas.commands()[1] {
722 DrawCommand::Rect { bounds, .. } => {
723 assert_eq!(bounds.width, 180.0);
724 }
725 _ => panic!("Expected Rect command for active portion"),
726 }
727 }
728
729 #[test]
730 fn test_slider_paint_thumb_size() {
731 let mut slider = Slider::new().thumb_radius(15.0);
732 slider.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
733
734 let mut canvas = RecordingCanvas::new();
735 slider.paint(&mut canvas);
736
737 match &canvas.commands()[2] {
739 DrawCommand::Rect { bounds, .. } => {
740 assert_eq!(bounds.width, 30.0);
741 assert_eq!(bounds.height, 30.0);
742 }
743 _ => panic!("Expected Rect command for thumb"),
744 }
745 }
746
747 #[test]
748 fn test_slider_paint_thumb_position_at_min() {
749 let mut slider = Slider::new().thumb_radius(10.0).value(0.0);
750 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
751
752 let mut canvas = RecordingCanvas::new();
753 slider.paint(&mut canvas);
754
755 match &canvas.commands()[2] {
758 DrawCommand::Rect { bounds, .. } => {
759 assert_eq!(bounds.x, 0.0);
760 }
761 _ => panic!("Expected Rect command for thumb"),
762 }
763 }
764
765 #[test]
766 fn test_slider_paint_thumb_position_at_max() {
767 let mut slider = Slider::new().thumb_radius(10.0).value(1.0);
768 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
769
770 let mut canvas = RecordingCanvas::new();
771 slider.paint(&mut canvas);
772
773 match &canvas.commands()[2] {
776 DrawCommand::Rect { bounds, .. } => {
777 assert_eq!(bounds.x, 180.0);
778 }
779 _ => panic!("Expected Rect command for thumb"),
780 }
781 }
782
783 #[test]
784 fn test_slider_paint_thumb_position_at_50_percent() {
785 let mut slider = Slider::new().thumb_radius(10.0).value(0.5);
786 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
787
788 let mut canvas = RecordingCanvas::new();
789 slider.paint(&mut canvas);
790
791 match &canvas.commands()[2] {
794 DrawCommand::Rect { bounds, .. } => {
795 assert_eq!(bounds.x, 90.0);
796 }
797 _ => panic!("Expected Rect command for thumb"),
798 }
799 }
800
801 #[test]
802 fn test_slider_paint_track_centered_vertically() {
803 let mut slider = Slider::new().track_height(4.0);
804 slider.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
805
806 let mut canvas = RecordingCanvas::new();
807 slider.paint(&mut canvas);
808
809 match &canvas.commands()[0] {
811 DrawCommand::Rect { bounds, .. } => {
812 assert_eq!(bounds.y, 13.0);
813 }
814 _ => panic!("Expected Rect command for track"),
815 }
816 }
817
818 #[test]
819 fn test_slider_paint_thumb_centered_vertically() {
820 let mut slider = Slider::new().thumb_radius(10.0);
821 slider.layout(Rect::new(0.0, 0.0, 200.0, 40.0));
822
823 let mut canvas = RecordingCanvas::new();
824 slider.paint(&mut canvas);
825
826 match &canvas.commands()[2] {
828 DrawCommand::Rect { bounds, .. } => {
829 assert_eq!(bounds.y, 10.0);
830 }
831 _ => panic!("Expected Rect command for thumb"),
832 }
833 }
834
835 #[test]
836 fn test_slider_paint_position_from_layout() {
837 let mut slider = Slider::new().thumb_radius(10.0);
838 slider.layout(Rect::new(50.0, 100.0, 200.0, 20.0));
839
840 let mut canvas = RecordingCanvas::new();
841 slider.paint(&mut canvas);
842
843 match &canvas.commands()[0] {
845 DrawCommand::Rect { bounds, .. } => {
846 assert_eq!(bounds.x, 60.0);
847 }
848 _ => panic!("Expected Rect command for track"),
849 }
850 }
851
852 #[test]
853 fn test_slider_paint_disabled_thumb_color() {
854 let mut slider = Slider::new().thumb_color(Color::WHITE).disabled(true);
855 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
856
857 let mut canvas = RecordingCanvas::new();
858 slider.paint(&mut canvas);
859
860 match &canvas.commands()[2] {
862 DrawCommand::Rect { style, .. } => {
863 let fill = style.fill.unwrap();
864 assert!((fill.r - 0.6).abs() < 0.01);
865 assert!((fill.g - 0.6).abs() < 0.01);
866 assert!((fill.b - 0.6).abs() < 0.01);
867 }
868 _ => panic!("Expected Rect command for thumb"),
869 }
870 }
871
872 #[test]
873 fn test_slider_paint_with_range() {
874 let mut slider = Slider::new()
875 .min(0.0)
876 .max(100.0)
877 .value(25.0)
878 .thumb_radius(10.0);
879 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
880
881 let mut canvas = RecordingCanvas::new();
882 slider.paint(&mut canvas);
883
884 match &canvas.commands()[1] {
886 DrawCommand::Rect { bounds, .. } => {
887 assert_eq!(bounds.width, 45.0);
888 }
889 _ => panic!("Expected Rect command for active portion"),
890 }
891 }
892
893 use presentar_core::Point;
898
899 #[test]
900 fn test_slider_event_mouse_down_starts_drag() {
901 let mut slider = Slider::new().thumb_radius(10.0);
902 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
903
904 assert!(!slider.dragging);
905 slider.event(&Event::MouseDown {
906 position: Point::new(100.0, 10.0),
907 button: MouseButton::Left,
908 });
909 assert!(slider.dragging);
910 }
911
912 #[test]
913 fn test_slider_event_mouse_down_updates_value() {
914 let mut slider = Slider::new()
915 .min(0.0)
916 .max(1.0)
917 .value(0.0)
918 .thumb_radius(10.0);
919 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
920
921 let result = slider.event(&Event::MouseDown {
924 position: Point::new(100.0, 10.0),
925 button: MouseButton::Left,
926 });
927
928 assert!((slider.get_value() - 0.5).abs() < 0.01);
929 assert!(result.is_some()); }
931
932 #[test]
933 fn test_slider_event_mouse_down_emits_slider_changed() {
934 let mut slider = Slider::new()
935 .min(0.0)
936 .max(100.0)
937 .value(0.0)
938 .thumb_radius(10.0);
939 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
940
941 let result = slider.event(&Event::MouseDown {
942 position: Point::new(100.0, 10.0),
943 button: MouseButton::Left,
944 });
945
946 let msg = result.unwrap().downcast::<SliderChanged>().unwrap();
947 assert!((msg.value - 50.0).abs() < 1.0); }
949
950 #[test]
951 fn test_slider_event_mouse_down_outside_bounds_no_drag() {
952 let mut slider = Slider::new().thumb_radius(10.0);
953 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
954
955 let result = slider.event(&Event::MouseDown {
956 position: Point::new(300.0, 10.0), button: MouseButton::Left,
958 });
959
960 assert!(!slider.dragging);
961 assert!(result.is_none());
962 }
963
964 #[test]
965 fn test_slider_event_mouse_down_right_button_no_drag() {
966 let mut slider = Slider::new().thumb_radius(10.0);
967 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
968
969 let result = slider.event(&Event::MouseDown {
970 position: Point::new(100.0, 10.0),
971 button: MouseButton::Right,
972 });
973
974 assert!(!slider.dragging);
975 assert!(result.is_none());
976 }
977
978 #[test]
979 fn test_slider_event_mouse_up_ends_drag() {
980 let mut slider = Slider::new().thumb_radius(10.0);
981 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
982
983 slider.event(&Event::MouseDown {
985 position: Point::new(100.0, 10.0),
986 button: MouseButton::Left,
987 });
988 assert!(slider.dragging);
989
990 let result = slider.event(&Event::MouseUp {
992 position: Point::new(100.0, 10.0),
993 button: MouseButton::Left,
994 });
995 assert!(!slider.dragging);
996 assert!(result.is_none()); }
998
999 #[test]
1000 fn test_slider_event_mouse_up_right_button_no_effect() {
1001 let mut slider = Slider::new().thumb_radius(10.0);
1002 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1003
1004 slider.event(&Event::MouseDown {
1006 position: Point::new(100.0, 10.0),
1007 button: MouseButton::Left,
1008 });
1009 assert!(slider.dragging);
1010
1011 slider.event(&Event::MouseUp {
1013 position: Point::new(100.0, 10.0),
1014 button: MouseButton::Right,
1015 });
1016 assert!(slider.dragging); }
1018
1019 #[test]
1020 fn test_slider_event_mouse_move_during_drag_updates_value() {
1021 let mut slider = Slider::new()
1022 .min(0.0)
1023 .max(1.0)
1024 .value(0.0)
1025 .thumb_radius(10.0);
1026 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1027
1028 slider.event(&Event::MouseDown {
1030 position: Point::new(10.0, 10.0),
1031 button: MouseButton::Left,
1032 });
1033
1034 let result = slider.event(&Event::MouseMove {
1036 position: Point::new(100.0, 10.0),
1037 });
1038
1039 assert!((slider.get_value() - 0.5).abs() < 0.01);
1040 assert!(result.is_some());
1041 }
1042
1043 #[test]
1044 fn test_slider_event_mouse_move_without_drag_no_effect() {
1045 let mut slider = Slider::new()
1046 .min(0.0)
1047 .max(1.0)
1048 .value(0.5)
1049 .thumb_radius(10.0);
1050 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1051
1052 let result = slider.event(&Event::MouseMove {
1053 position: Point::new(190.0, 10.0),
1054 });
1055
1056 assert_eq!(slider.get_value(), 0.5); assert!(result.is_none());
1058 }
1059
1060 #[test]
1061 fn test_slider_event_drag_to_minimum() {
1062 let mut slider = Slider::new()
1063 .min(0.0)
1064 .max(100.0)
1065 .value(50.0)
1066 .thumb_radius(10.0);
1067 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1068
1069 slider.event(&Event::MouseDown {
1071 position: Point::new(100.0, 10.0),
1072 button: MouseButton::Left,
1073 });
1074
1075 slider.event(&Event::MouseMove {
1077 position: Point::new(-50.0, 10.0),
1078 });
1079
1080 assert_eq!(slider.get_value(), 0.0);
1081 }
1082
1083 #[test]
1084 fn test_slider_event_drag_to_maximum() {
1085 let mut slider = Slider::new()
1086 .min(0.0)
1087 .max(100.0)
1088 .value(50.0)
1089 .thumb_radius(10.0);
1090 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1091
1092 slider.event(&Event::MouseDown {
1094 position: Point::new(100.0, 10.0),
1095 button: MouseButton::Left,
1096 });
1097
1098 slider.event(&Event::MouseMove {
1100 position: Point::new(300.0, 10.0),
1101 });
1102
1103 assert_eq!(slider.get_value(), 100.0);
1104 }
1105
1106 #[test]
1107 fn test_slider_event_drag_with_step() {
1108 let mut slider = Slider::new()
1109 .min(0.0)
1110 .max(100.0)
1111 .value(0.0)
1112 .step(25.0)
1113 .thumb_radius(10.0);
1114 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1115
1116 slider.event(&Event::MouseDown {
1118 position: Point::new(10.0, 10.0),
1119 button: MouseButton::Left,
1120 });
1121
1122 slider.event(&Event::MouseMove {
1124 position: Point::new(64.0, 10.0), });
1126
1127 assert_eq!(slider.get_value(), 25.0);
1128 }
1129
1130 #[test]
1131 fn test_slider_event_disabled_blocks_mouse_down() {
1132 let mut slider = Slider::new().value(0.5).disabled(true).thumb_radius(10.0);
1133 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1134
1135 let result = slider.event(&Event::MouseDown {
1136 position: Point::new(100.0, 10.0),
1137 button: MouseButton::Left,
1138 });
1139
1140 assert!(!slider.dragging);
1141 assert_eq!(slider.get_value(), 0.5); assert!(result.is_none());
1143 }
1144
1145 #[test]
1146 fn test_slider_event_disabled_blocks_mouse_move() {
1147 let mut slider = Slider::new().value(0.5).disabled(true).thumb_radius(10.0);
1148 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1149 slider.dragging = true; let result = slider.event(&Event::MouseMove {
1152 position: Point::new(190.0, 10.0),
1153 });
1154
1155 assert_eq!(slider.get_value(), 0.5); assert!(result.is_none());
1157 }
1158
1159 #[test]
1160 fn test_slider_event_no_message_when_value_unchanged() {
1161 let mut slider = Slider::new()
1162 .min(0.0)
1163 .max(1.0)
1164 .value(0.5)
1165 .thumb_radius(10.0);
1166 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1167
1168 let result = slider.event(&Event::MouseDown {
1170 position: Point::new(100.0, 10.0), button: MouseButton::Left,
1172 });
1173
1174 assert!(result.is_none());
1176 }
1177
1178 #[test]
1179 fn test_slider_event_full_drag_flow() {
1180 let mut slider = Slider::new()
1181 .min(0.0)
1182 .max(100.0)
1183 .value(0.0)
1184 .thumb_radius(10.0);
1185 slider.layout(Rect::new(0.0, 0.0, 200.0, 20.0));
1186
1187 let result1 = slider.event(&Event::MouseDown {
1189 position: Point::new(10.0, 10.0),
1190 button: MouseButton::Left,
1191 });
1192 assert!(slider.dragging);
1193 assert!(result1.is_none()); let result2 = slider.event(&Event::MouseMove {
1197 position: Point::new(55.0, 10.0),
1198 });
1199 assert!((slider.get_value() - 25.0).abs() < 1.0);
1200 assert!(result2.is_some());
1201
1202 let result3 = slider.event(&Event::MouseMove {
1204 position: Point::new(145.0, 10.0),
1205 });
1206 assert!((slider.get_value() - 75.0).abs() < 1.0);
1207 assert!(result3.is_some());
1208
1209 let result4 = slider.event(&Event::MouseUp {
1211 position: Point::new(145.0, 10.0),
1212 button: MouseButton::Left,
1213 });
1214 assert!(!slider.dragging);
1215 assert!(result4.is_none());
1216
1217 let result5 = slider.event(&Event::MouseMove {
1219 position: Point::new(10.0, 10.0),
1220 });
1221 assert!((slider.get_value() - 75.0).abs() < 1.0); assert!(result5.is_none());
1223 }
1224
1225 #[test]
1226 fn test_slider_event_bounds_with_offset() {
1227 let mut slider = Slider::new()
1228 .min(0.0)
1229 .max(100.0)
1230 .value(0.0)
1231 .thumb_radius(10.0);
1232 slider.layout(Rect::new(50.0, 100.0, 200.0, 20.0));
1233
1234 slider.event(&Event::MouseDown {
1237 position: Point::new(150.0, 110.0),
1238 button: MouseButton::Left,
1239 });
1240
1241 assert!((slider.get_value() - 50.0).abs() < 1.0);
1242 }
1243}