1use presentar_core::{
4 widget::{AccessibleRole, FontWeight, LayoutResult, TextStyle},
5 Brick, BrickAssertion, BrickBudget, BrickVerification, Canvas, Color, Constraints,
6 CornerRadius, Event, MouseButton, Point, Rect, Size, TypeId, Widget,
7};
8use serde::{Deserialize, Serialize};
9use std::any::Any;
10use std::time::Duration;
11
12#[derive(Clone, Serialize, Deserialize)]
14pub struct Button {
15 label: String,
17 background: Color,
19 background_hover: Color,
21 background_pressed: Color,
23 text_color: Color,
25 corner_radius: CornerRadius,
27 padding: f32,
29 font_size: f32,
31 disabled: bool,
33 test_id_value: Option<String>,
35 accessible_name: Option<String>,
37 #[serde(skip)]
39 hovered: bool,
40 #[serde(skip)]
42 pressed: bool,
43 #[serde(skip)]
45 bounds: Rect,
46}
47
48#[derive(Debug, Clone)]
50pub struct ButtonClicked;
51
52impl Button {
53 #[must_use]
55 pub fn new(label: impl Into<String>) -> Self {
56 Self {
57 label: label.into(),
58 background: Color::from_hex("#6366f1").unwrap_or(Color::BLACK),
59 background_hover: Color::from_hex("#4f46e5").unwrap_or(Color::BLACK),
60 background_pressed: Color::from_hex("#4338ca").unwrap_or(Color::BLACK),
61 text_color: Color::WHITE,
62 corner_radius: CornerRadius::uniform(4.0),
63 padding: 12.0,
64 font_size: 14.0,
65 disabled: false,
66 test_id_value: None,
67 accessible_name: None,
68 hovered: false,
69 pressed: false,
70 bounds: Rect::default(),
71 }
72 }
73
74 #[must_use]
76 pub const fn background(mut self, color: Color) -> Self {
77 self.background = color;
78 self
79 }
80
81 #[must_use]
83 pub const fn background_hover(mut self, color: Color) -> Self {
84 self.background_hover = color;
85 self
86 }
87
88 #[must_use]
90 pub const fn background_pressed(mut self, color: Color) -> Self {
91 self.background_pressed = color;
92 self
93 }
94
95 #[must_use]
97 pub const fn text_color(mut self, color: Color) -> Self {
98 self.text_color = color;
99 self
100 }
101
102 #[must_use]
104 pub const fn corner_radius(mut self, radius: CornerRadius) -> Self {
105 self.corner_radius = radius;
106 self
107 }
108
109 #[must_use]
111 pub const fn padding(mut self, padding: f32) -> Self {
112 self.padding = padding;
113 self
114 }
115
116 #[must_use]
118 pub const fn font_size(mut self, size: f32) -> Self {
119 self.font_size = size;
120 self
121 }
122
123 #[must_use]
125 pub const fn disabled(mut self, disabled: bool) -> Self {
126 self.disabled = disabled;
127 self
128 }
129
130 #[must_use]
132 pub fn with_test_id(mut self, id: impl Into<String>) -> Self {
133 self.test_id_value = Some(id.into());
134 self
135 }
136
137 #[must_use]
139 pub fn with_accessible_name(mut self, name: impl Into<String>) -> Self {
140 self.accessible_name = Some(name.into());
141 self
142 }
143
144 fn current_background(&self) -> Color {
146 if self.disabled {
147 let gray = (self.background.r + self.background.g + self.background.b) / 3.0;
149 Color::rgb(gray, gray, gray)
150 } else if self.pressed {
151 self.background_pressed
152 } else if self.hovered {
153 self.background_hover
154 } else {
155 self.background
156 }
157 }
158
159 fn estimate_text_size(&self) -> Size {
161 let char_width = self.font_size * 0.6;
162 let width = self.label.len() as f32 * char_width;
163 let height = self.font_size * 1.2;
164 Size::new(width, height)
165 }
166}
167
168impl Widget for Button {
169 fn type_id(&self) -> TypeId {
170 TypeId::of::<Self>()
171 }
172
173 fn measure(&self, constraints: Constraints) -> Size {
174 let text_size = self.estimate_text_size();
175 let size = Size::new(
176 self.padding.mul_add(2.0, text_size.width),
177 self.padding.mul_add(2.0, text_size.height),
178 );
179 constraints.constrain(size)
180 }
181
182 fn layout(&mut self, bounds: Rect) -> LayoutResult {
183 self.bounds = bounds;
184 LayoutResult {
185 size: bounds.size(),
186 }
187 }
188
189 fn paint(&self, canvas: &mut dyn Canvas) {
190 canvas.fill_rect(self.bounds, self.current_background());
192
193 let text_size = self.estimate_text_size();
195 let text_pos = Point::new(
196 self.bounds.x + (self.bounds.width - text_size.width) / 2.0,
197 self.bounds.y + (self.bounds.height - text_size.height) / 2.0,
198 );
199
200 let style = TextStyle {
201 size: self.font_size,
202 color: if self.disabled {
203 Color::rgb(0.7, 0.7, 0.7)
204 } else {
205 self.text_color
206 },
207 weight: FontWeight::Medium,
208 ..Default::default()
209 };
210
211 canvas.draw_text(&self.label, text_pos, &style);
212 }
213
214 fn event(&mut self, event: &Event) -> Option<Box<dyn Any + Send>> {
215 if self.disabled {
216 return None;
217 }
218
219 match event {
220 Event::MouseEnter => {
221 self.hovered = true;
222 None
223 }
224 Event::MouseLeave => {
225 self.hovered = false;
226 self.pressed = false;
227 None
228 }
229 Event::MouseDown {
230 position,
231 button: MouseButton::Left,
232 } => {
233 if self.bounds.contains_point(position) {
234 self.pressed = true;
235 }
236 None
237 }
238 Event::MouseUp {
239 position,
240 button: MouseButton::Left,
241 } => {
242 let was_pressed = self.pressed;
243 self.pressed = false;
244
245 if was_pressed && self.bounds.contains_point(position) {
246 Some(Box::new(ButtonClicked))
247 } else {
248 None
249 }
250 }
251 Event::KeyDown {
252 key: presentar_core::Key::Enter | presentar_core::Key::Space,
253 ..
254 } => {
255 self.pressed = true;
256 None
257 }
258 Event::KeyUp {
259 key: presentar_core::Key::Enter | presentar_core::Key::Space,
260 ..
261 } => {
262 self.pressed = false;
263 Some(Box::new(ButtonClicked))
264 }
265 _ => None,
266 }
267 }
268
269 fn children(&self) -> &[Box<dyn Widget>] {
270 &[]
271 }
272
273 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
274 &mut []
275 }
276
277 fn is_interactive(&self) -> bool {
278 !self.disabled
279 }
280
281 fn is_focusable(&self) -> bool {
282 !self.disabled
283 }
284
285 fn accessible_name(&self) -> Option<&str> {
286 self.accessible_name.as_deref().or(Some(&self.label))
287 }
288
289 fn accessible_role(&self) -> AccessibleRole {
290 AccessibleRole::Button
291 }
292
293 fn test_id(&self) -> Option<&str> {
294 self.test_id_value.as_deref()
295 }
296}
297
298impl Brick for Button {
300 fn brick_name(&self) -> &'static str {
301 "Button"
302 }
303
304 fn assertions(&self) -> &[BrickAssertion] {
305 &[
307 BrickAssertion::TextVisible,
308 BrickAssertion::ContrastRatio(4.5), ]
310 }
311
312 fn budget(&self) -> BrickBudget {
313 BrickBudget::uniform(16) }
315
316 fn verify(&self) -> BrickVerification {
317 let mut passed = Vec::new();
318 let mut failed = Vec::new();
319
320 let bg = self.current_background();
322 let contrast = bg.contrast_ratio(&self.text_color);
323 if contrast >= 4.5 {
324 passed.push(BrickAssertion::ContrastRatio(4.5));
325 } else {
326 failed.push((
327 BrickAssertion::ContrastRatio(4.5),
328 format!("Contrast ratio {contrast:.2}:1 < 4.5:1"),
329 ));
330 }
331
332 if self.label.is_empty() {
334 failed.push((BrickAssertion::TextVisible, "Button has no label".into()));
335 } else {
336 passed.push(BrickAssertion::TextVisible);
337 }
338
339 BrickVerification {
340 passed,
341 failed,
342 verification_time: Duration::from_micros(10),
343 }
344 }
345
346 fn to_html(&self) -> String {
347 let disabled = if self.disabled { " disabled" } else { "" };
348 let test_id = self.test_id_value.as_deref().unwrap_or("button");
349 format!(
350 r#"<button class="brick-button" data-testid="{}" aria-label="{}"{}>{}</button>"#,
351 test_id,
352 self.accessible_name.as_deref().unwrap_or(&self.label),
353 disabled,
354 self.label
355 )
356 }
357
358 fn to_css(&self) -> String {
359 format!(
360 r".brick-button {{
361 background: {};
362 color: {};
363 padding: {}px;
364 font-size: {}px;
365 border: none;
366 border-radius: {}px;
367 cursor: pointer;
368}}
369.brick-button:hover {{ background: {}; }}
370.brick-button:active {{ background: {}; }}
371.brick-button:disabled {{ opacity: 0.5; cursor: not-allowed; }}",
372 self.background.to_hex(),
373 self.text_color.to_hex(),
374 self.padding,
375 self.font_size,
376 self.corner_radius.top_left,
377 self.background_hover.to_hex(),
378 self.background_pressed.to_hex(),
379 )
380 }
381
382 fn test_id(&self) -> Option<&str> {
383 self.test_id_value.as_deref()
384 }
385}
386
387#[cfg(test)]
388#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
389mod tests {
390 use super::*;
391 use presentar_core::draw::DrawCommand;
392 use presentar_core::{RecordingCanvas, Widget};
393
394 #[test]
395 fn test_button_new() {
396 let b = Button::new("Click me");
397 assert_eq!(b.label, "Click me");
398 assert!(!b.disabled);
399 }
400
401 #[test]
402 fn test_button_builder() {
403 let b = Button::new("Test")
404 .padding(20.0)
405 .font_size(18.0)
406 .disabled(true)
407 .with_test_id("my-button");
408
409 assert_eq!(b.padding, 20.0);
410 assert_eq!(b.font_size, 18.0);
411 assert!(b.disabled);
412 assert_eq!(Widget::test_id(&b), Some("my-button"));
413 }
414
415 #[test]
416 fn test_button_accessible() {
417 let b = Button::new("OK");
418 assert_eq!(Widget::accessible_name(&b), Some("OK"));
419 assert_eq!(Widget::accessible_role(&b), AccessibleRole::Button);
420 assert!(Widget::is_focusable(&b));
421 }
422
423 #[test]
424 fn test_button_disabled_not_focusable() {
425 let b = Button::new("OK").disabled(true);
426 assert!(!Widget::is_focusable(&b));
427 assert!(!Widget::is_interactive(&b));
428 }
429
430 #[test]
431 fn test_button_measure() {
432 let b = Button::new("Test");
433 let size = b.measure(Constraints::loose(Size::new(1000.0, 1000.0)));
434 assert!(size.width > 0.0);
435 assert!(size.height > 0.0);
436 }
437
438 #[test]
441 fn test_button_paint_draws_background() {
442 let mut button = Button::new("Click");
443 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
444
445 let mut canvas = RecordingCanvas::new();
446 button.paint(&mut canvas);
447
448 assert!(canvas.command_count() >= 2);
450
451 match &canvas.commands()[0] {
453 DrawCommand::Rect { bounds, style, .. } => {
454 assert_eq!(bounds.width, 100.0);
455 assert_eq!(bounds.height, 40.0);
456 assert!(style.fill.is_some());
457 }
458 _ => panic!("Expected Rect command for background"),
459 }
460 }
461
462 #[test]
463 fn test_button_paint_draws_text() {
464 let mut button = Button::new("Hello");
465 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
466
467 let mut canvas = RecordingCanvas::new();
468 button.paint(&mut canvas);
469
470 let has_text = canvas
472 .commands()
473 .iter()
474 .any(|cmd| matches!(cmd, DrawCommand::Text { content, .. } if content == "Hello"));
475 assert!(has_text, "Should draw button label text");
476 }
477
478 #[test]
479 fn test_button_paint_disabled_uses_gray() {
480 let mut button = Button::new("Disabled").disabled(true);
481 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
482
483 let mut canvas = RecordingCanvas::new();
484 button.paint(&mut canvas);
485
486 let text_cmd = canvas
488 .commands()
489 .iter()
490 .find(|cmd| matches!(cmd, DrawCommand::Text { .. }));
491
492 if let Some(DrawCommand::Text { style, .. }) = text_cmd {
493 assert!(style.color.r > 0.5 && style.color.g > 0.5 && style.color.b > 0.5);
495 } else {
496 panic!("Expected Text command");
497 }
498 }
499
500 #[test]
501 fn test_button_paint_hovered_uses_hover_color() {
502 let mut button = Button::new("Hover")
503 .background(Color::RED)
504 .background_hover(Color::BLUE);
505 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
506
507 button.event(&Event::MouseEnter);
509
510 let mut canvas = RecordingCanvas::new();
511 button.paint(&mut canvas);
512
513 match &canvas.commands()[0] {
515 DrawCommand::Rect { style, .. } => {
516 assert_eq!(style.fill, Some(Color::BLUE));
517 }
518 _ => panic!("Expected Rect command"),
519 }
520 }
521
522 #[test]
523 fn test_button_paint_pressed_uses_pressed_color() {
524 let mut button = Button::new("Press")
525 .background(Color::RED)
526 .background_pressed(Color::GREEN);
527 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
528
529 button.event(&Event::MouseEnter);
531 button.event(&Event::MouseDown {
532 position: Point::new(50.0, 20.0),
533 button: MouseButton::Left,
534 });
535
536 let mut canvas = RecordingCanvas::new();
537 button.paint(&mut canvas);
538
539 match &canvas.commands()[0] {
541 DrawCommand::Rect { style, .. } => {
542 assert_eq!(style.fill, Some(Color::GREEN));
543 }
544 _ => panic!("Expected Rect command"),
545 }
546 }
547
548 #[test]
549 fn test_button_paint_text_centered() {
550 let mut button = Button::new("X");
551 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
552
553 let mut canvas = RecordingCanvas::new();
554 button.paint(&mut canvas);
555
556 let text_cmd = canvas
558 .commands()
559 .iter()
560 .find(|cmd| matches!(cmd, DrawCommand::Text { .. }));
561
562 if let Some(DrawCommand::Text { position, .. }) = text_cmd {
563 assert!(position.x > 10.0 && position.x < 90.0);
565 assert!(position.y > 5.0 && position.y < 35.0);
566 } else {
567 panic!("Expected Text command");
568 }
569 }
570
571 #[test]
572 fn test_button_paint_custom_colors() {
573 let mut button = Button::new("Custom")
574 .background(Color::rgb(1.0, 0.0, 0.0))
575 .text_color(Color::rgb(0.0, 1.0, 0.0));
576 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
577
578 let mut canvas = RecordingCanvas::new();
579 button.paint(&mut canvas);
580
581 match &canvas.commands()[0] {
583 DrawCommand::Rect { style, .. } => {
584 let fill = style.fill.unwrap();
585 assert!((fill.r - 1.0).abs() < 0.01);
586 assert!(fill.g < 0.01);
587 assert!(fill.b < 0.01);
588 }
589 _ => panic!("Expected Rect command"),
590 }
591
592 let text_cmd = canvas
594 .commands()
595 .iter()
596 .find(|cmd| matches!(cmd, DrawCommand::Text { .. }));
597 if let Some(DrawCommand::Text { style, .. }) = text_cmd {
598 assert!(style.color.r < 0.01);
599 assert!((style.color.g - 1.0).abs() < 0.01);
600 assert!(style.color.b < 0.01);
601 }
602 }
603
604 use presentar_core::Key;
607
608 #[test]
609 fn test_button_event_mouse_enter_sets_hovered() {
610 let mut button = Button::new("Test");
611 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
612
613 assert!(!button.hovered);
614 let result = button.event(&Event::MouseEnter);
615 assert!(button.hovered);
616 assert!(result.is_none()); }
618
619 #[test]
620 fn test_button_event_mouse_leave_clears_hovered() {
621 let mut button = Button::new("Test");
622 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
623
624 button.event(&Event::MouseEnter);
625 assert!(button.hovered);
626
627 let result = button.event(&Event::MouseLeave);
628 assert!(!button.hovered);
629 assert!(result.is_none());
630 }
631
632 #[test]
633 fn test_button_event_mouse_leave_clears_pressed() {
634 let mut button = Button::new("Test");
635 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
636
637 button.event(&Event::MouseEnter);
639 button.event(&Event::MouseDown {
640 position: Point::new(50.0, 20.0),
641 button: MouseButton::Left,
642 });
643 assert!(button.pressed);
644
645 button.event(&Event::MouseLeave);
647 assert!(!button.pressed);
648 assert!(!button.hovered);
649 }
650
651 #[test]
652 fn test_button_event_mouse_down_sets_pressed() {
653 let mut button = Button::new("Test");
654 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
655
656 assert!(!button.pressed);
657 let result = button.event(&Event::MouseDown {
658 position: Point::new(50.0, 20.0),
659 button: MouseButton::Left,
660 });
661 assert!(button.pressed);
662 assert!(result.is_none()); }
664
665 #[test]
666 fn test_button_event_mouse_down_outside_bounds_no_press() {
667 let mut button = Button::new("Test");
668 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
669
670 let result = button.event(&Event::MouseDown {
671 position: Point::new(150.0, 20.0), button: MouseButton::Left,
673 });
674 assert!(!button.pressed);
675 assert!(result.is_none());
676 }
677
678 #[test]
679 fn test_button_event_mouse_down_right_button_no_press() {
680 let mut button = Button::new("Test");
681 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
682
683 let result = button.event(&Event::MouseDown {
684 position: Point::new(50.0, 20.0),
685 button: MouseButton::Right,
686 });
687 assert!(!button.pressed);
688 assert!(result.is_none());
689 }
690
691 #[test]
692 fn test_button_event_mouse_up_emits_clicked() {
693 let mut button = Button::new("Test");
694 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
695
696 button.event(&Event::MouseDown {
698 position: Point::new(50.0, 20.0),
699 button: MouseButton::Left,
700 });
701 assert!(button.pressed);
702
703 let result = button.event(&Event::MouseUp {
705 position: Point::new(50.0, 20.0),
706 button: MouseButton::Left,
707 });
708 assert!(!button.pressed);
709 assert!(result.is_some());
710
711 let _msg: Box<ButtonClicked> = result.unwrap().downcast::<ButtonClicked>().unwrap();
713 }
714
715 #[test]
716 fn test_button_event_mouse_up_outside_bounds_no_click() {
717 let mut button = Button::new("Test");
718 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
719
720 button.event(&Event::MouseDown {
722 position: Point::new(50.0, 20.0),
723 button: MouseButton::Left,
724 });
725 assert!(button.pressed);
726
727 let result = button.event(&Event::MouseUp {
729 position: Point::new(150.0, 20.0),
730 button: MouseButton::Left,
731 });
732 assert!(!button.pressed);
733 assert!(result.is_none()); }
735
736 #[test]
737 fn test_button_event_mouse_up_without_prior_press_no_click() {
738 let mut button = Button::new("Test");
739 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
740
741 let result = button.event(&Event::MouseUp {
743 position: Point::new(50.0, 20.0),
744 button: MouseButton::Left,
745 });
746 assert!(result.is_none());
747 }
748
749 #[test]
750 fn test_button_event_mouse_up_right_button_no_effect() {
751 let mut button = Button::new("Test");
752 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
753
754 button.event(&Event::MouseDown {
756 position: Point::new(50.0, 20.0),
757 button: MouseButton::Left,
758 });
759
760 let result = button.event(&Event::MouseUp {
762 position: Point::new(50.0, 20.0),
763 button: MouseButton::Right,
764 });
765 assert!(button.pressed); assert!(result.is_none());
767 }
768
769 #[test]
770 fn test_button_event_key_down_enter_sets_pressed() {
771 let mut button = Button::new("Test");
772 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
773
774 let result = button.event(&Event::key_down(Key::Enter));
775 assert!(button.pressed);
776 assert!(result.is_none()); }
778
779 #[test]
780 fn test_button_event_key_down_space_sets_pressed() {
781 let mut button = Button::new("Test");
782 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
783
784 let result = button.event(&Event::key_down(Key::Space));
785 assert!(button.pressed);
786 assert!(result.is_none());
787 }
788
789 #[test]
790 fn test_button_event_key_up_enter_emits_clicked() {
791 let mut button = Button::new("Test");
792 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
793
794 button.event(&Event::key_down(Key::Enter));
796 assert!(button.pressed);
797
798 let result = button.event(&Event::key_up(Key::Enter));
800 assert!(!button.pressed);
801 assert!(result.is_some());
802 }
803
804 #[test]
805 fn test_button_event_key_up_space_emits_clicked() {
806 let mut button = Button::new("Test");
807 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
808
809 button.event(&Event::key_down(Key::Space));
810 let result = button.event(&Event::key_up(Key::Space));
811 assert!(!button.pressed);
812 assert!(result.is_some());
813 }
814
815 #[test]
816 fn test_button_event_key_other_no_effect() {
817 let mut button = Button::new("Test");
818 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
819
820 let result = button.event(&Event::key_down(Key::Escape));
821 assert!(!button.pressed);
822 assert!(result.is_none());
823 }
824
825 #[test]
826 fn test_button_event_disabled_blocks_mouse_enter() {
827 let mut button = Button::new("Test").disabled(true);
828 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
829
830 let result = button.event(&Event::MouseEnter);
831 assert!(!button.hovered);
832 assert!(result.is_none());
833 }
834
835 #[test]
836 fn test_button_event_disabled_blocks_mouse_down() {
837 let mut button = Button::new("Test").disabled(true);
838 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
839
840 let result = button.event(&Event::MouseDown {
841 position: Point::new(50.0, 20.0),
842 button: MouseButton::Left,
843 });
844 assert!(!button.pressed);
845 assert!(result.is_none());
846 }
847
848 #[test]
849 fn test_button_event_disabled_blocks_key_down() {
850 let mut button = Button::new("Test").disabled(true);
851 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
852
853 let result = button.event(&Event::key_down(Key::Enter));
854 assert!(!button.pressed);
855 assert!(result.is_none());
856 }
857
858 #[test]
859 fn test_button_event_disabled_blocks_key_up() {
860 let mut button = Button::new("Test").disabled(true);
861 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
862
863 let result = button.event(&Event::key_up(Key::Enter));
864 assert!(result.is_none());
865 }
866
867 #[test]
868 fn test_button_click_full_interaction_flow() {
869 let mut button = Button::new("Submit");
870 button.layout(Rect::new(10.0, 10.0, 100.0, 40.0));
871
872 button.event(&Event::MouseEnter);
874 assert!(button.hovered);
875 assert!(!button.pressed);
876
877 button.event(&Event::MouseDown {
878 position: Point::new(50.0, 25.0),
879 button: MouseButton::Left,
880 });
881 assert!(button.hovered);
882 assert!(button.pressed);
883
884 let result = button.event(&Event::MouseUp {
885 position: Point::new(50.0, 25.0),
886 button: MouseButton::Left,
887 });
888 assert!(button.hovered);
889 assert!(!button.pressed);
890 assert!(result.is_some()); button.event(&Event::MouseLeave);
893 assert!(!button.hovered);
894 assert!(!button.pressed);
895 }
896
897 #[test]
898 fn test_button_drag_out_and_release_no_click() {
899 let mut button = Button::new("Drag");
900 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
901
902 button.event(&Event::MouseEnter);
904 button.event(&Event::MouseDown {
905 position: Point::new(50.0, 20.0),
906 button: MouseButton::Left,
907 });
908 assert!(button.pressed);
909
910 button.event(&Event::MouseLeave);
912 assert!(!button.pressed); let result = button.event(&Event::MouseUp {
916 position: Point::new(150.0, 20.0),
917 button: MouseButton::Left,
918 });
919 assert!(result.is_none()); }
921
922 #[test]
923 fn test_button_event_bounds_edge_cases() {
924 let mut button = Button::new("Edge");
925 button.layout(Rect::new(10.0, 20.0, 100.0, 40.0));
926
927 button.event(&Event::MouseDown {
929 position: Point::new(10.0, 20.0),
930 button: MouseButton::Left,
931 });
932 assert!(button.pressed);
933 button.pressed = false;
934
935 button.event(&Event::MouseDown {
937 position: Point::new(109.9, 59.9),
938 button: MouseButton::Left,
939 });
940 assert!(button.pressed);
941 button.pressed = false;
942
943 button.event(&Event::MouseDown {
945 position: Point::new(111.0, 30.0),
946 button: MouseButton::Left,
947 });
948 assert!(!button.pressed);
949 }
950}