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 self.pressed = true;
255 None
256 }
257 Event::KeyUp {
258 key: presentar_core::Key::Enter | presentar_core::Key::Space,
259 } => {
260 self.pressed = false;
261 Some(Box::new(ButtonClicked))
262 }
263 _ => None,
264 }
265 }
266
267 fn children(&self) -> &[Box<dyn Widget>] {
268 &[]
269 }
270
271 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
272 &mut []
273 }
274
275 fn is_interactive(&self) -> bool {
276 !self.disabled
277 }
278
279 fn is_focusable(&self) -> bool {
280 !self.disabled
281 }
282
283 fn accessible_name(&self) -> Option<&str> {
284 self.accessible_name.as_deref().or(Some(&self.label))
285 }
286
287 fn accessible_role(&self) -> AccessibleRole {
288 AccessibleRole::Button
289 }
290
291 fn test_id(&self) -> Option<&str> {
292 self.test_id_value.as_deref()
293 }
294}
295
296impl Brick for Button {
298 fn brick_name(&self) -> &'static str {
299 "Button"
300 }
301
302 fn assertions(&self) -> &[BrickAssertion] {
303 &[
305 BrickAssertion::TextVisible,
306 BrickAssertion::ContrastRatio(4.5), ]
308 }
309
310 fn budget(&self) -> BrickBudget {
311 BrickBudget::uniform(16) }
313
314 fn verify(&self) -> BrickVerification {
315 let mut passed = Vec::new();
316 let mut failed = Vec::new();
317
318 let bg = self.current_background();
320 let contrast = bg.contrast_ratio(&self.text_color);
321 if contrast >= 4.5 {
322 passed.push(BrickAssertion::ContrastRatio(4.5));
323 } else {
324 failed.push((
325 BrickAssertion::ContrastRatio(4.5),
326 format!("Contrast ratio {contrast:.2}:1 < 4.5:1"),
327 ));
328 }
329
330 if self.label.is_empty() {
332 failed.push((BrickAssertion::TextVisible, "Button has no label".into()));
333 } else {
334 passed.push(BrickAssertion::TextVisible);
335 }
336
337 BrickVerification {
338 passed,
339 failed,
340 verification_time: Duration::from_micros(10),
341 }
342 }
343
344 fn to_html(&self) -> String {
345 let disabled = if self.disabled { " disabled" } else { "" };
346 let test_id = self.test_id_value.as_deref().unwrap_or("button");
347 format!(
348 r#"<button class="brick-button" data-testid="{}" aria-label="{}"{}>{}</button>"#,
349 test_id,
350 self.accessible_name.as_deref().unwrap_or(&self.label),
351 disabled,
352 self.label
353 )
354 }
355
356 fn to_css(&self) -> String {
357 format!(
358 r".brick-button {{
359 background: {};
360 color: {};
361 padding: {}px;
362 font-size: {}px;
363 border: none;
364 border-radius: {}px;
365 cursor: pointer;
366}}
367.brick-button:hover {{ background: {}; }}
368.brick-button:active {{ background: {}; }}
369.brick-button:disabled {{ opacity: 0.5; cursor: not-allowed; }}",
370 self.background.to_hex(),
371 self.text_color.to_hex(),
372 self.padding,
373 self.font_size,
374 self.corner_radius.top_left,
375 self.background_hover.to_hex(),
376 self.background_pressed.to_hex(),
377 )
378 }
379
380 fn test_id(&self) -> Option<&str> {
381 self.test_id_value.as_deref()
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388 use presentar_core::draw::DrawCommand;
389 use presentar_core::{RecordingCanvas, Widget};
390
391 #[test]
392 fn test_button_new() {
393 let b = Button::new("Click me");
394 assert_eq!(b.label, "Click me");
395 assert!(!b.disabled);
396 }
397
398 #[test]
399 fn test_button_builder() {
400 let b = Button::new("Test")
401 .padding(20.0)
402 .font_size(18.0)
403 .disabled(true)
404 .with_test_id("my-button");
405
406 assert_eq!(b.padding, 20.0);
407 assert_eq!(b.font_size, 18.0);
408 assert!(b.disabled);
409 assert_eq!(Widget::test_id(&b), Some("my-button"));
410 }
411
412 #[test]
413 fn test_button_accessible() {
414 let b = Button::new("OK");
415 assert_eq!(Widget::accessible_name(&b), Some("OK"));
416 assert_eq!(Widget::accessible_role(&b), AccessibleRole::Button);
417 assert!(Widget::is_focusable(&b));
418 }
419
420 #[test]
421 fn test_button_disabled_not_focusable() {
422 let b = Button::new("OK").disabled(true);
423 assert!(!Widget::is_focusable(&b));
424 assert!(!Widget::is_interactive(&b));
425 }
426
427 #[test]
428 fn test_button_measure() {
429 let b = Button::new("Test");
430 let size = b.measure(Constraints::loose(Size::new(1000.0, 1000.0)));
431 assert!(size.width > 0.0);
432 assert!(size.height > 0.0);
433 }
434
435 #[test]
438 fn test_button_paint_draws_background() {
439 let mut button = Button::new("Click");
440 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
441
442 let mut canvas = RecordingCanvas::new();
443 button.paint(&mut canvas);
444
445 assert!(canvas.command_count() >= 2);
447
448 match &canvas.commands()[0] {
450 DrawCommand::Rect { bounds, style, .. } => {
451 assert_eq!(bounds.width, 100.0);
452 assert_eq!(bounds.height, 40.0);
453 assert!(style.fill.is_some());
454 }
455 _ => panic!("Expected Rect command for background"),
456 }
457 }
458
459 #[test]
460 fn test_button_paint_draws_text() {
461 let mut button = Button::new("Hello");
462 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
463
464 let mut canvas = RecordingCanvas::new();
465 button.paint(&mut canvas);
466
467 let has_text = canvas
469 .commands()
470 .iter()
471 .any(|cmd| matches!(cmd, DrawCommand::Text { content, .. } if content == "Hello"));
472 assert!(has_text, "Should draw button label text");
473 }
474
475 #[test]
476 fn test_button_paint_disabled_uses_gray() {
477 let mut button = Button::new("Disabled").disabled(true);
478 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
479
480 let mut canvas = RecordingCanvas::new();
481 button.paint(&mut canvas);
482
483 let text_cmd = canvas
485 .commands()
486 .iter()
487 .find(|cmd| matches!(cmd, DrawCommand::Text { .. }));
488
489 if let Some(DrawCommand::Text { style, .. }) = text_cmd {
490 assert!(style.color.r > 0.5 && style.color.g > 0.5 && style.color.b > 0.5);
492 } else {
493 panic!("Expected Text command");
494 }
495 }
496
497 #[test]
498 fn test_button_paint_hovered_uses_hover_color() {
499 let mut button = Button::new("Hover")
500 .background(Color::RED)
501 .background_hover(Color::BLUE);
502 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
503
504 button.event(&Event::MouseEnter);
506
507 let mut canvas = RecordingCanvas::new();
508 button.paint(&mut canvas);
509
510 match &canvas.commands()[0] {
512 DrawCommand::Rect { style, .. } => {
513 assert_eq!(style.fill, Some(Color::BLUE));
514 }
515 _ => panic!("Expected Rect command"),
516 }
517 }
518
519 #[test]
520 fn test_button_paint_pressed_uses_pressed_color() {
521 let mut button = Button::new("Press")
522 .background(Color::RED)
523 .background_pressed(Color::GREEN);
524 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
525
526 button.event(&Event::MouseEnter);
528 button.event(&Event::MouseDown {
529 position: Point::new(50.0, 20.0),
530 button: MouseButton::Left,
531 });
532
533 let mut canvas = RecordingCanvas::new();
534 button.paint(&mut canvas);
535
536 match &canvas.commands()[0] {
538 DrawCommand::Rect { style, .. } => {
539 assert_eq!(style.fill, Some(Color::GREEN));
540 }
541 _ => panic!("Expected Rect command"),
542 }
543 }
544
545 #[test]
546 fn test_button_paint_text_centered() {
547 let mut button = Button::new("X");
548 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
549
550 let mut canvas = RecordingCanvas::new();
551 button.paint(&mut canvas);
552
553 let text_cmd = canvas
555 .commands()
556 .iter()
557 .find(|cmd| matches!(cmd, DrawCommand::Text { .. }));
558
559 if let Some(DrawCommand::Text { position, .. }) = text_cmd {
560 assert!(position.x > 10.0 && position.x < 90.0);
562 assert!(position.y > 5.0 && position.y < 35.0);
563 } else {
564 panic!("Expected Text command");
565 }
566 }
567
568 #[test]
569 fn test_button_paint_custom_colors() {
570 let mut button = Button::new("Custom")
571 .background(Color::rgb(1.0, 0.0, 0.0))
572 .text_color(Color::rgb(0.0, 1.0, 0.0));
573 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
574
575 let mut canvas = RecordingCanvas::new();
576 button.paint(&mut canvas);
577
578 match &canvas.commands()[0] {
580 DrawCommand::Rect { style, .. } => {
581 let fill = style.fill.unwrap();
582 assert!((fill.r - 1.0).abs() < 0.01);
583 assert!(fill.g < 0.01);
584 assert!(fill.b < 0.01);
585 }
586 _ => panic!("Expected Rect command"),
587 }
588
589 let text_cmd = canvas
591 .commands()
592 .iter()
593 .find(|cmd| matches!(cmd, DrawCommand::Text { .. }));
594 if let Some(DrawCommand::Text { style, .. }) = text_cmd {
595 assert!(style.color.r < 0.01);
596 assert!((style.color.g - 1.0).abs() < 0.01);
597 assert!(style.color.b < 0.01);
598 }
599 }
600
601 use presentar_core::Key;
604
605 #[test]
606 fn test_button_event_mouse_enter_sets_hovered() {
607 let mut button = Button::new("Test");
608 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
609
610 assert!(!button.hovered);
611 let result = button.event(&Event::MouseEnter);
612 assert!(button.hovered);
613 assert!(result.is_none()); }
615
616 #[test]
617 fn test_button_event_mouse_leave_clears_hovered() {
618 let mut button = Button::new("Test");
619 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
620
621 button.event(&Event::MouseEnter);
622 assert!(button.hovered);
623
624 let result = button.event(&Event::MouseLeave);
625 assert!(!button.hovered);
626 assert!(result.is_none());
627 }
628
629 #[test]
630 fn test_button_event_mouse_leave_clears_pressed() {
631 let mut button = Button::new("Test");
632 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
633
634 button.event(&Event::MouseEnter);
636 button.event(&Event::MouseDown {
637 position: Point::new(50.0, 20.0),
638 button: MouseButton::Left,
639 });
640 assert!(button.pressed);
641
642 button.event(&Event::MouseLeave);
644 assert!(!button.pressed);
645 assert!(!button.hovered);
646 }
647
648 #[test]
649 fn test_button_event_mouse_down_sets_pressed() {
650 let mut button = Button::new("Test");
651 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
652
653 assert!(!button.pressed);
654 let result = button.event(&Event::MouseDown {
655 position: Point::new(50.0, 20.0),
656 button: MouseButton::Left,
657 });
658 assert!(button.pressed);
659 assert!(result.is_none()); }
661
662 #[test]
663 fn test_button_event_mouse_down_outside_bounds_no_press() {
664 let mut button = Button::new("Test");
665 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
666
667 let result = button.event(&Event::MouseDown {
668 position: Point::new(150.0, 20.0), button: MouseButton::Left,
670 });
671 assert!(!button.pressed);
672 assert!(result.is_none());
673 }
674
675 #[test]
676 fn test_button_event_mouse_down_right_button_no_press() {
677 let mut button = Button::new("Test");
678 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
679
680 let result = button.event(&Event::MouseDown {
681 position: Point::new(50.0, 20.0),
682 button: MouseButton::Right,
683 });
684 assert!(!button.pressed);
685 assert!(result.is_none());
686 }
687
688 #[test]
689 fn test_button_event_mouse_up_emits_clicked() {
690 let mut button = Button::new("Test");
691 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
692
693 button.event(&Event::MouseDown {
695 position: Point::new(50.0, 20.0),
696 button: MouseButton::Left,
697 });
698 assert!(button.pressed);
699
700 let result = button.event(&Event::MouseUp {
702 position: Point::new(50.0, 20.0),
703 button: MouseButton::Left,
704 });
705 assert!(!button.pressed);
706 assert!(result.is_some());
707
708 let _msg: Box<ButtonClicked> = result.unwrap().downcast::<ButtonClicked>().unwrap();
710 }
711
712 #[test]
713 fn test_button_event_mouse_up_outside_bounds_no_click() {
714 let mut button = Button::new("Test");
715 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
716
717 button.event(&Event::MouseDown {
719 position: Point::new(50.0, 20.0),
720 button: MouseButton::Left,
721 });
722 assert!(button.pressed);
723
724 let result = button.event(&Event::MouseUp {
726 position: Point::new(150.0, 20.0),
727 button: MouseButton::Left,
728 });
729 assert!(!button.pressed);
730 assert!(result.is_none()); }
732
733 #[test]
734 fn test_button_event_mouse_up_without_prior_press_no_click() {
735 let mut button = Button::new("Test");
736 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
737
738 let result = button.event(&Event::MouseUp {
740 position: Point::new(50.0, 20.0),
741 button: MouseButton::Left,
742 });
743 assert!(result.is_none());
744 }
745
746 #[test]
747 fn test_button_event_mouse_up_right_button_no_effect() {
748 let mut button = Button::new("Test");
749 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
750
751 button.event(&Event::MouseDown {
753 position: Point::new(50.0, 20.0),
754 button: MouseButton::Left,
755 });
756
757 let result = button.event(&Event::MouseUp {
759 position: Point::new(50.0, 20.0),
760 button: MouseButton::Right,
761 });
762 assert!(button.pressed); assert!(result.is_none());
764 }
765
766 #[test]
767 fn test_button_event_key_down_enter_sets_pressed() {
768 let mut button = Button::new("Test");
769 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
770
771 let result = button.event(&Event::KeyDown { key: Key::Enter });
772 assert!(button.pressed);
773 assert!(result.is_none()); }
775
776 #[test]
777 fn test_button_event_key_down_space_sets_pressed() {
778 let mut button = Button::new("Test");
779 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
780
781 let result = button.event(&Event::KeyDown { key: Key::Space });
782 assert!(button.pressed);
783 assert!(result.is_none());
784 }
785
786 #[test]
787 fn test_button_event_key_up_enter_emits_clicked() {
788 let mut button = Button::new("Test");
789 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
790
791 button.event(&Event::KeyDown { key: Key::Enter });
793 assert!(button.pressed);
794
795 let result = button.event(&Event::KeyUp { key: Key::Enter });
797 assert!(!button.pressed);
798 assert!(result.is_some());
799 }
800
801 #[test]
802 fn test_button_event_key_up_space_emits_clicked() {
803 let mut button = Button::new("Test");
804 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
805
806 button.event(&Event::KeyDown { key: Key::Space });
807 let result = button.event(&Event::KeyUp { key: Key::Space });
808 assert!(!button.pressed);
809 assert!(result.is_some());
810 }
811
812 #[test]
813 fn test_button_event_key_other_no_effect() {
814 let mut button = Button::new("Test");
815 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
816
817 let result = button.event(&Event::KeyDown { key: Key::Escape });
818 assert!(!button.pressed);
819 assert!(result.is_none());
820 }
821
822 #[test]
823 fn test_button_event_disabled_blocks_mouse_enter() {
824 let mut button = Button::new("Test").disabled(true);
825 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
826
827 let result = button.event(&Event::MouseEnter);
828 assert!(!button.hovered);
829 assert!(result.is_none());
830 }
831
832 #[test]
833 fn test_button_event_disabled_blocks_mouse_down() {
834 let mut button = Button::new("Test").disabled(true);
835 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
836
837 let result = button.event(&Event::MouseDown {
838 position: Point::new(50.0, 20.0),
839 button: MouseButton::Left,
840 });
841 assert!(!button.pressed);
842 assert!(result.is_none());
843 }
844
845 #[test]
846 fn test_button_event_disabled_blocks_key_down() {
847 let mut button = Button::new("Test").disabled(true);
848 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
849
850 let result = button.event(&Event::KeyDown { key: Key::Enter });
851 assert!(!button.pressed);
852 assert!(result.is_none());
853 }
854
855 #[test]
856 fn test_button_event_disabled_blocks_key_up() {
857 let mut button = Button::new("Test").disabled(true);
858 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
859
860 let result = button.event(&Event::KeyUp { key: Key::Enter });
861 assert!(result.is_none());
862 }
863
864 #[test]
865 fn test_button_click_full_interaction_flow() {
866 let mut button = Button::new("Submit");
867 button.layout(Rect::new(10.0, 10.0, 100.0, 40.0));
868
869 button.event(&Event::MouseEnter);
871 assert!(button.hovered);
872 assert!(!button.pressed);
873
874 button.event(&Event::MouseDown {
875 position: Point::new(50.0, 25.0),
876 button: MouseButton::Left,
877 });
878 assert!(button.hovered);
879 assert!(button.pressed);
880
881 let result = button.event(&Event::MouseUp {
882 position: Point::new(50.0, 25.0),
883 button: MouseButton::Left,
884 });
885 assert!(button.hovered);
886 assert!(!button.pressed);
887 assert!(result.is_some()); button.event(&Event::MouseLeave);
890 assert!(!button.hovered);
891 assert!(!button.pressed);
892 }
893
894 #[test]
895 fn test_button_drag_out_and_release_no_click() {
896 let mut button = Button::new("Drag");
897 button.layout(Rect::new(0.0, 0.0, 100.0, 40.0));
898
899 button.event(&Event::MouseEnter);
901 button.event(&Event::MouseDown {
902 position: Point::new(50.0, 20.0),
903 button: MouseButton::Left,
904 });
905 assert!(button.pressed);
906
907 button.event(&Event::MouseLeave);
909 assert!(!button.pressed); let result = button.event(&Event::MouseUp {
913 position: Point::new(150.0, 20.0),
914 button: MouseButton::Left,
915 });
916 assert!(result.is_none()); }
918
919 #[test]
920 fn test_button_event_bounds_edge_cases() {
921 let mut button = Button::new("Edge");
922 button.layout(Rect::new(10.0, 20.0, 100.0, 40.0));
923
924 button.event(&Event::MouseDown {
926 position: Point::new(10.0, 20.0),
927 button: MouseButton::Left,
928 });
929 assert!(button.pressed);
930 button.pressed = false;
931
932 button.event(&Event::MouseDown {
934 position: Point::new(109.9, 59.9),
935 button: MouseButton::Left,
936 });
937 assert!(button.pressed);
938 button.pressed = false;
939
940 button.event(&Event::MouseDown {
942 position: Point::new(111.0, 30.0),
943 button: MouseButton::Left,
944 });
945 assert!(!button.pressed);
946 }
947}