1use presentar_core::{
4 widget::{AccessibleRole, LayoutResult, TextStyle},
5 Canvas, Color, Constraints, Event, Key, Rect, Size, TypeId, Widget,
6};
7use serde::{Deserialize, Serialize};
8use std::any::Any;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct TextChanged {
13 pub value: String,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct TextSubmitted {
20 pub value: String,
22}
23
24#[derive(Serialize, Deserialize)]
26pub struct TextInput {
27 value: String,
29 placeholder: String,
31 disabled: bool,
33 obscure: bool,
35 max_length: usize,
37 text_style: TextStyle,
39 placeholder_color: Color,
41 background_color: Color,
43 border_color: Color,
45 focus_border_color: Color,
47 padding: f32,
49 min_width: f32,
51 test_id_value: Option<String>,
53 accessible_name_value: Option<String>,
55 #[serde(skip)]
57 bounds: Rect,
58 #[serde(skip)]
60 focused: bool,
61 #[serde(skip)]
63 cursor: usize,
64}
65
66impl Default for TextInput {
67 fn default() -> Self {
68 Self::new()
69 }
70}
71
72impl TextInput {
73 #[must_use]
75 pub fn new() -> Self {
76 Self {
77 value: String::new(),
78 placeholder: String::new(),
79 disabled: false,
80 obscure: false,
81 max_length: 0,
82 text_style: TextStyle::default(),
83 placeholder_color: Color::new(0.6, 0.6, 0.6, 1.0),
84 background_color: Color::WHITE,
85 border_color: Color::new(0.8, 0.8, 0.8, 1.0),
86 focus_border_color: Color::new(0.2, 0.6, 1.0, 1.0),
87 padding: 8.0,
88 min_width: 100.0,
89 test_id_value: None,
90 accessible_name_value: None,
91 bounds: Rect::default(),
92 focused: false,
93 cursor: 0,
94 }
95 }
96
97 #[must_use]
99 pub fn value(mut self, value: impl Into<String>) -> Self {
100 self.value = value.into();
101 if self.max_length > 0 && self.value.len() > self.max_length {
102 self.value.truncate(self.max_length);
103 }
104 self.cursor = self.value.len();
105 self
106 }
107
108 #[must_use]
110 pub fn placeholder(mut self, text: impl Into<String>) -> Self {
111 self.placeholder = text.into();
112 self
113 }
114
115 #[must_use]
117 pub const fn disabled(mut self, disabled: bool) -> Self {
118 self.disabled = disabled;
119 self
120 }
121
122 #[must_use]
124 pub const fn obscure(mut self, obscure: bool) -> Self {
125 self.obscure = obscure;
126 self
127 }
128
129 #[must_use]
131 pub fn max_length(mut self, max: usize) -> Self {
132 self.max_length = max;
133 if max > 0 && self.value.len() > max {
134 self.value.truncate(max);
135 self.cursor = self.cursor.min(max);
136 }
137 self
138 }
139
140 #[must_use]
142 pub const fn text_style(mut self, style: TextStyle) -> Self {
143 self.text_style = style;
144 self
145 }
146
147 #[must_use]
149 pub const fn placeholder_color(mut self, color: Color) -> Self {
150 self.placeholder_color = color;
151 self
152 }
153
154 #[must_use]
156 pub const fn background_color(mut self, color: Color) -> Self {
157 self.background_color = color;
158 self
159 }
160
161 #[must_use]
163 pub const fn border_color(mut self, color: Color) -> Self {
164 self.border_color = color;
165 self
166 }
167
168 #[must_use]
170 pub const fn focus_border_color(mut self, color: Color) -> Self {
171 self.focus_border_color = color;
172 self
173 }
174
175 #[must_use]
177 pub fn padding(mut self, padding: f32) -> Self {
178 self.padding = padding.max(0.0);
179 self
180 }
181
182 #[must_use]
184 pub fn min_width(mut self, width: f32) -> Self {
185 self.min_width = width.max(0.0);
186 self
187 }
188
189 #[must_use]
191 pub fn with_test_id(mut self, id: impl Into<String>) -> Self {
192 self.test_id_value = Some(id.into());
193 self
194 }
195
196 #[must_use]
198 pub fn with_accessible_name(mut self, name: impl Into<String>) -> Self {
199 self.accessible_name_value = Some(name.into());
200 self
201 }
202
203 #[must_use]
205 pub fn get_value(&self) -> &str {
206 &self.value
207 }
208
209 #[must_use]
211 pub fn get_placeholder(&self) -> &str {
212 &self.placeholder
213 }
214
215 #[must_use]
217 pub fn is_empty(&self) -> bool {
218 self.value.is_empty()
219 }
220
221 #[must_use]
223 pub const fn cursor_position(&self) -> usize {
224 self.cursor
225 }
226
227 #[must_use]
229 pub const fn is_focused(&self) -> bool {
230 self.focused
231 }
232
233 #[must_use]
235 pub fn display_text(&self) -> String {
236 if self.obscure {
237 "•".repeat(self.value.len())
238 } else {
239 self.value.clone()
240 }
241 }
242
243 fn insert_text(&mut self, text: &str) -> bool {
245 if self.disabled {
246 return false;
247 }
248
249 let mut changed = false;
250 for c in text.chars() {
251 if self.max_length > 0 && self.value.len() >= self.max_length {
252 break;
253 }
254 self.value.insert(self.cursor, c);
255 self.cursor += 1;
256 changed = true;
257 }
258 changed
259 }
260
261 fn backspace(&mut self) -> bool {
263 if self.disabled || self.cursor == 0 {
264 return false;
265 }
266 self.cursor -= 1;
267 self.value.remove(self.cursor);
268 true
269 }
270
271 fn delete(&mut self) -> bool {
273 if self.disabled || self.cursor >= self.value.len() {
274 return false;
275 }
276 self.value.remove(self.cursor);
277 true
278 }
279
280 fn move_left(&mut self) {
282 if self.cursor > 0 {
283 self.cursor -= 1;
284 }
285 }
286
287 fn move_right(&mut self) {
289 if self.cursor < self.value.len() {
290 self.cursor += 1;
291 }
292 }
293
294 fn move_home(&mut self) {
296 self.cursor = 0;
297 }
298
299 fn move_end(&mut self) {
301 self.cursor = self.value.len();
302 }
303}
304
305impl Widget for TextInput {
306 fn type_id(&self) -> TypeId {
307 TypeId::of::<Self>()
308 }
309
310 fn measure(&self, constraints: Constraints) -> Size {
311 let height = 2.0f32.mul_add(self.padding, self.text_style.size);
312 let width = self.min_width.max(constraints.min_width);
313 constraints.constrain(Size::new(width, height))
314 }
315
316 fn layout(&mut self, bounds: Rect) -> LayoutResult {
317 self.bounds = bounds;
318 LayoutResult {
319 size: bounds.size(),
320 }
321 }
322
323 fn paint(&self, canvas: &mut dyn Canvas) {
324 canvas.fill_rect(self.bounds, self.background_color);
326
327 let border_color = if self.focused {
329 self.focus_border_color
330 } else {
331 self.border_color
332 };
333 canvas.stroke_rect(self.bounds, border_color, 1.0);
334
335 let text_x = self.bounds.x + self.padding;
337 let text_y = self.bounds.y + self.padding;
338 let position = presentar_core::Point::new(text_x, text_y);
339
340 if self.value.is_empty() {
341 let mut placeholder_style = self.text_style.clone();
343 placeholder_style.color = self.placeholder_color;
344 canvas.draw_text(&self.placeholder, position, &placeholder_style);
345 } else {
346 let display = self.display_text();
348 canvas.draw_text(&display, position, &self.text_style);
349 }
350 }
351
352 fn event(&mut self, event: &Event) -> Option<Box<dyn Any + Send>> {
353 if self.disabled {
354 return None;
355 }
356
357 match event {
358 Event::MouseDown { position, .. } => {
359 let was_focused = self.focused;
360 self.focused = self.bounds.contains_point(position);
361 if self.focused && !was_focused {
362 self.cursor = self.value.len();
363 }
364 }
365 Event::FocusIn => {
366 self.focused = true;
367 }
368 Event::FocusOut => {
369 self.focused = false;
370 }
371 Event::TextInput { text } if self.focused => {
372 if self.insert_text(text) {
373 return Some(Box::new(TextChanged {
374 value: self.value.clone(),
375 }));
376 }
377 }
378 Event::KeyDown { key } if self.focused => match key {
379 Key::Backspace => {
380 if self.backspace() {
381 return Some(Box::new(TextChanged {
382 value: self.value.clone(),
383 }));
384 }
385 }
386 Key::Delete => {
387 if self.delete() {
388 return Some(Box::new(TextChanged {
389 value: self.value.clone(),
390 }));
391 }
392 }
393 Key::Left => self.move_left(),
394 Key::Right => self.move_right(),
395 Key::Home => self.move_home(),
396 Key::End => self.move_end(),
397 Key::Enter => {
398 return Some(Box::new(TextSubmitted {
399 value: self.value.clone(),
400 }));
401 }
402 _ => {}
403 },
404 _ => {}
405 }
406
407 None
408 }
409
410 fn children(&self) -> &[Box<dyn Widget>] {
411 &[]
412 }
413
414 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
415 &mut []
416 }
417
418 fn is_interactive(&self) -> bool {
419 !self.disabled
420 }
421
422 fn is_focusable(&self) -> bool {
423 !self.disabled
424 }
425
426 fn accessible_name(&self) -> Option<&str> {
427 self.accessible_name_value.as_deref()
428 }
429
430 fn accessible_role(&self) -> AccessibleRole {
431 AccessibleRole::TextInput
432 }
433
434 fn test_id(&self) -> Option<&str> {
435 self.test_id_value.as_deref()
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442 use presentar_core::Widget;
443
444 #[test]
449 fn test_text_changed_message() {
450 let msg = TextChanged {
451 value: "hello".to_string(),
452 };
453 assert_eq!(msg.value, "hello");
454 }
455
456 #[test]
457 fn test_text_submitted_message() {
458 let msg = TextSubmitted {
459 value: "world".to_string(),
460 };
461 assert_eq!(msg.value, "world");
462 }
463
464 #[test]
469 fn test_text_input_new() {
470 let input = TextInput::new();
471 assert!(input.get_value().is_empty());
472 assert!(input.get_placeholder().is_empty());
473 assert!(input.is_empty());
474 assert!(!input.disabled);
475 assert!(!input.obscure);
476 }
477
478 #[test]
479 fn test_text_input_default() {
480 let input = TextInput::default();
481 assert!(input.is_empty());
482 }
483
484 #[test]
485 fn test_text_input_builder() {
486 let input = TextInput::new()
487 .value("hello")
488 .placeholder("Enter text...")
489 .disabled(true)
490 .obscure(true)
491 .max_length(20)
492 .padding(10.0)
493 .min_width(200.0)
494 .with_test_id("my-input")
495 .with_accessible_name("Email");
496
497 assert_eq!(input.get_value(), "hello");
498 assert_eq!(input.get_placeholder(), "Enter text...");
499 assert!(input.disabled);
500 assert!(input.obscure);
501 assert_eq!(input.max_length, 20);
502 assert_eq!(Widget::test_id(&input), Some("my-input"));
503 assert_eq!(input.accessible_name(), Some("Email"));
504 }
505
506 #[test]
511 fn test_text_input_value() {
512 let input = TextInput::new().value("test");
513 assert_eq!(input.get_value(), "test");
514 assert!(!input.is_empty());
515 }
516
517 #[test]
518 fn test_text_input_max_length_truncate() {
519 let input = TextInput::new().max_length(5).value("hello world");
520 assert_eq!(input.get_value(), "hello");
521 }
522
523 #[test]
524 fn test_text_input_cursor_position() {
525 let input = TextInput::new().value("hello");
526 assert_eq!(input.cursor_position(), 5); }
528
529 #[test]
534 fn test_text_input_display_normal() {
535 let input = TextInput::new().value("password");
536 assert_eq!(input.display_text(), "password");
537 }
538
539 #[test]
540 fn test_text_input_display_obscured() {
541 let input = TextInput::new().value("secret").obscure(true);
542 assert_eq!(input.display_text(), "••••••");
543 }
544
545 #[test]
550 fn test_text_input_insert() {
551 let mut input = TextInput::new().value("hlo");
552 input.cursor = 1;
553 input.insert_text("el");
554 assert_eq!(input.get_value(), "hello");
555 assert_eq!(input.cursor_position(), 3);
556 }
557
558 #[test]
559 fn test_text_input_insert_respects_max_length() {
560 let mut input = TextInput::new().max_length(5).value("abc");
561 input.insert_text("defgh");
562 assert_eq!(input.get_value(), "abcde");
563 }
564
565 #[test]
566 fn test_text_input_backspace() {
567 let mut input = TextInput::new().value("hello");
568 input.backspace();
569 assert_eq!(input.get_value(), "hell");
570 assert_eq!(input.cursor_position(), 4);
571 }
572
573 #[test]
574 fn test_text_input_backspace_at_start() {
575 let mut input = TextInput::new().value("hello");
576 input.cursor = 0;
577 let changed = input.backspace();
578 assert!(!changed);
579 assert_eq!(input.get_value(), "hello");
580 }
581
582 #[test]
583 fn test_text_input_delete() {
584 let mut input = TextInput::new().value("hello");
585 input.cursor = 0;
586 input.delete();
587 assert_eq!(input.get_value(), "ello");
588 assert_eq!(input.cursor_position(), 0);
589 }
590
591 #[test]
592 fn test_text_input_delete_at_end() {
593 let mut input = TextInput::new().value("hello");
594 let changed = input.delete();
595 assert!(!changed);
596 assert_eq!(input.get_value(), "hello");
597 }
598
599 #[test]
604 fn test_text_input_move_left() {
605 let mut input = TextInput::new().value("hello");
606 input.move_left();
607 assert_eq!(input.cursor_position(), 4);
608 }
609
610 #[test]
611 fn test_text_input_move_left_at_start() {
612 let mut input = TextInput::new().value("hello");
613 input.cursor = 0;
614 input.move_left();
615 assert_eq!(input.cursor_position(), 0); }
617
618 #[test]
619 fn test_text_input_move_right() {
620 let mut input = TextInput::new().value("hello");
621 input.cursor = 2;
622 input.move_right();
623 assert_eq!(input.cursor_position(), 3);
624 }
625
626 #[test]
627 fn test_text_input_move_right_at_end() {
628 let mut input = TextInput::new().value("hello");
629 input.move_right();
630 assert_eq!(input.cursor_position(), 5); }
632
633 #[test]
634 fn test_text_input_move_home() {
635 let mut input = TextInput::new().value("hello");
636 input.move_home();
637 assert_eq!(input.cursor_position(), 0);
638 }
639
640 #[test]
641 fn test_text_input_move_end() {
642 let mut input = TextInput::new().value("hello");
643 input.cursor = 0;
644 input.move_end();
645 assert_eq!(input.cursor_position(), 5);
646 }
647
648 #[test]
653 fn test_text_input_type_id() {
654 let input = TextInput::new();
655 assert_eq!(Widget::type_id(&input), TypeId::of::<TextInput>());
656 }
657
658 #[test]
659 fn test_text_input_measure() {
660 let input = TextInput::new();
661 let size = input.measure(Constraints::loose(Size::new(400.0, 100.0)));
662 assert!(size.width >= 100.0);
663 assert!(size.height > 0.0);
664 }
665
666 #[test]
667 fn test_text_input_is_interactive() {
668 let input = TextInput::new();
669 assert!(input.is_interactive());
670
671 let input = TextInput::new().disabled(true);
672 assert!(!input.is_interactive());
673 }
674
675 #[test]
676 fn test_text_input_is_focusable() {
677 let input = TextInput::new();
678 assert!(input.is_focusable());
679
680 let input = TextInput::new().disabled(true);
681 assert!(!input.is_focusable());
682 }
683
684 #[test]
685 fn test_text_input_accessible_role() {
686 let input = TextInput::new();
687 assert_eq!(input.accessible_role(), AccessibleRole::TextInput);
688 }
689
690 #[test]
691 fn test_text_input_children() {
692 let input = TextInput::new();
693 assert!(input.children().is_empty());
694 }
695
696 #[test]
701 fn test_text_input_colors() {
702 let input = TextInput::new()
703 .background_color(Color::RED)
704 .border_color(Color::GREEN)
705 .focus_border_color(Color::BLUE)
706 .placeholder_color(Color::YELLOW);
707
708 assert_eq!(input.background_color, Color::RED);
709 assert_eq!(input.border_color, Color::GREEN);
710 assert_eq!(input.focus_border_color, Color::BLUE);
711 assert_eq!(input.placeholder_color, Color::YELLOW);
712 }
713
714 #[test]
719 fn test_text_input_focus_state() {
720 let input = TextInput::new();
721 assert!(!input.is_focused());
722 }
723
724 #[test]
725 fn test_text_input_disabled_no_insert() {
726 let mut input = TextInput::new().disabled(true);
727 let changed = input.insert_text("test");
728 assert!(!changed);
729 assert!(input.is_empty());
730 }
731
732 #[test]
733 fn test_text_input_disabled_no_backspace() {
734 let mut input = TextInput::new().value("test").disabled(true);
735 input.disabled = true; let changed = input.backspace();
737 assert!(!changed);
738 }
739
740 #[test]
741 fn test_text_input_disabled_no_delete() {
742 let mut input = TextInput::new().value("test").disabled(true);
743 input.disabled = true;
744 input.cursor = 0;
745 let changed = input.delete();
746 assert!(!changed);
747 }
748
749 use presentar_core::{Key, MouseButton, Point};
754
755 #[test]
756 fn test_text_input_event_focus_in() {
757 let mut input = TextInput::new();
758 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
759
760 assert!(!input.focused);
761 let result = input.event(&Event::FocusIn);
762 assert!(input.focused);
763 assert!(result.is_none()); }
765
766 #[test]
767 fn test_text_input_event_focus_out() {
768 let mut input = TextInput::new();
769 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
770
771 input.event(&Event::FocusIn);
772 assert!(input.focused);
773
774 let result = input.event(&Event::FocusOut);
775 assert!(!input.focused);
776 assert!(result.is_none());
777 }
778
779 #[test]
780 fn test_text_input_event_mouse_down_inside_focuses() {
781 let mut input = TextInput::new();
782 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
783
784 assert!(!input.focused);
785 let result = input.event(&Event::MouseDown {
786 position: Point::new(100.0, 15.0),
787 button: MouseButton::Left,
788 });
789 assert!(input.focused);
790 assert!(result.is_none());
791 }
792
793 #[test]
794 fn test_text_input_event_mouse_down_outside_unfocuses() {
795 let mut input = TextInput::new();
796 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
797 input.focused = true;
798
799 let result = input.event(&Event::MouseDown {
800 position: Point::new(300.0, 15.0),
801 button: MouseButton::Left,
802 });
803 assert!(!input.focused);
804 assert!(result.is_none());
805 }
806
807 #[test]
808 fn test_text_input_event_mouse_down_sets_cursor() {
809 let mut input = TextInput::new().value("hello");
810 input.cursor = 0; input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
812
813 input.event(&Event::MouseDown {
815 position: Point::new(100.0, 15.0),
816 button: MouseButton::Left,
817 });
818 assert_eq!(input.cursor, 5); }
820
821 #[test]
822 fn test_text_input_event_text_input_when_focused() {
823 let mut input = TextInput::new();
824 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
825 input.event(&Event::FocusIn);
826
827 let result = input.event(&Event::TextInput {
828 text: "hello".to_string(),
829 });
830 assert_eq!(input.get_value(), "hello");
831 assert!(result.is_some());
832
833 let msg = result.unwrap().downcast::<TextChanged>().unwrap();
834 assert_eq!(msg.value, "hello");
835 }
836
837 #[test]
838 fn test_text_input_event_text_input_when_not_focused() {
839 let mut input = TextInput::new();
840 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
841 let result = input.event(&Event::TextInput {
844 text: "hello".to_string(),
845 });
846 assert!(input.get_value().is_empty());
847 assert!(result.is_none());
848 }
849
850 #[test]
851 fn test_text_input_event_key_backspace() {
852 let mut input = TextInput::new().value("hello");
853 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
854 input.event(&Event::FocusIn);
855 input.cursor = 5;
856
857 let result = input.event(&Event::KeyDown {
858 key: Key::Backspace,
859 });
860 assert_eq!(input.get_value(), "hell");
861 assert!(result.is_some());
862
863 let msg = result.unwrap().downcast::<TextChanged>().unwrap();
864 assert_eq!(msg.value, "hell");
865 }
866
867 #[test]
868 fn test_text_input_event_key_delete() {
869 let mut input = TextInput::new().value("hello");
870 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
871 input.event(&Event::FocusIn);
872 input.cursor = 0;
873
874 let result = input.event(&Event::KeyDown { key: Key::Delete });
875 assert_eq!(input.get_value(), "ello");
876 assert!(result.is_some());
877 }
878
879 #[test]
880 fn test_text_input_event_key_left() {
881 let mut input = TextInput::new().value("hello");
882 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
883 input.event(&Event::FocusIn);
884 input.cursor = 3;
885
886 let result = input.event(&Event::KeyDown { key: Key::Left });
887 assert_eq!(input.cursor, 2);
888 assert!(result.is_none()); }
890
891 #[test]
892 fn test_text_input_event_key_right() {
893 let mut input = TextInput::new().value("hello");
894 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
895 input.event(&Event::FocusIn);
896 input.cursor = 2;
897
898 let result = input.event(&Event::KeyDown { key: Key::Right });
899 assert_eq!(input.cursor, 3);
900 assert!(result.is_none());
901 }
902
903 #[test]
904 fn test_text_input_event_key_home() {
905 let mut input = TextInput::new().value("hello");
906 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
907 input.event(&Event::FocusIn);
908 input.cursor = 5;
909
910 let result = input.event(&Event::KeyDown { key: Key::Home });
911 assert_eq!(input.cursor, 0);
912 assert!(result.is_none());
913 }
914
915 #[test]
916 fn test_text_input_event_key_end() {
917 let mut input = TextInput::new().value("hello");
918 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
919 input.event(&Event::FocusIn);
920 input.cursor = 0;
921
922 let result = input.event(&Event::KeyDown { key: Key::End });
923 assert_eq!(input.cursor, 5);
924 assert!(result.is_none());
925 }
926
927 #[test]
928 fn test_text_input_event_key_enter_submits() {
929 let mut input = TextInput::new().value("hello");
930 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
931 input.event(&Event::FocusIn);
932
933 let result = input.event(&Event::KeyDown { key: Key::Enter });
934 assert!(result.is_some());
935
936 let msg = result.unwrap().downcast::<TextSubmitted>().unwrap();
937 assert_eq!(msg.value, "hello");
938 }
939
940 #[test]
941 fn test_text_input_event_key_when_not_focused() {
942 let mut input = TextInput::new().value("hello");
943 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
944 let result = input.event(&Event::KeyDown {
947 key: Key::Backspace,
948 });
949 assert_eq!(input.get_value(), "hello"); assert!(result.is_none());
951 }
952
953 #[test]
954 fn test_text_input_event_disabled_blocks_focus() {
955 let mut input = TextInput::new().disabled(true);
956 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
957
958 let result = input.event(&Event::FocusIn);
959 assert!(!input.focused);
960 assert!(result.is_none());
961 }
962
963 #[test]
964 fn test_text_input_event_disabled_blocks_mouse_down() {
965 let mut input = TextInput::new().disabled(true);
966 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
967
968 let result = input.event(&Event::MouseDown {
969 position: Point::new(100.0, 15.0),
970 button: MouseButton::Left,
971 });
972 assert!(!input.focused);
973 assert!(result.is_none());
974 }
975
976 #[test]
977 fn test_text_input_event_disabled_blocks_text_input() {
978 let mut input = TextInput::new().disabled(true);
979 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
980 input.focused = true; let result = input.event(&Event::TextInput {
983 text: "hello".to_string(),
984 });
985 assert!(input.get_value().is_empty());
986 assert!(result.is_none());
987 }
988
989 #[test]
990 fn test_text_input_event_disabled_blocks_key_down() {
991 let mut input = TextInput::new().value("hello").disabled(true);
992 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
993 input.focused = true;
994
995 let result = input.event(&Event::KeyDown {
996 key: Key::Backspace,
997 });
998 assert_eq!(input.get_value(), "hello");
999 assert!(result.is_none());
1000 }
1001
1002 #[test]
1003 fn test_text_input_event_full_typing_flow() {
1004 let mut input = TextInput::new();
1005 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
1006
1007 input.event(&Event::MouseDown {
1009 position: Point::new(100.0, 15.0),
1010 button: MouseButton::Left,
1011 });
1012 assert!(input.focused);
1013
1014 input.event(&Event::TextInput {
1016 text: "Hello".to_string(),
1017 });
1018 assert_eq!(input.get_value(), "Hello");
1019 assert_eq!(input.cursor, 5);
1020
1021 input.event(&Event::KeyDown {
1023 key: Key::Backspace,
1024 });
1025 assert_eq!(input.get_value(), "Hell");
1026 assert_eq!(input.cursor, 4);
1027
1028 input.event(&Event::KeyDown { key: Key::Home });
1030 assert_eq!(input.cursor, 0);
1031
1032 input.event(&Event::TextInput {
1034 text: "Say ".to_string(),
1035 });
1036 assert_eq!(input.get_value(), "Say Hell");
1037 assert_eq!(input.cursor, 4);
1038
1039 input.event(&Event::KeyDown { key: Key::End });
1041 assert_eq!(input.cursor, 8);
1042
1043 input.event(&Event::TextInput {
1045 text: "o".to_string(),
1046 });
1047 assert_eq!(input.get_value(), "Say Hello");
1048
1049 let result = input.event(&Event::KeyDown { key: Key::Enter });
1051 let msg = result.unwrap().downcast::<TextSubmitted>().unwrap();
1052 assert_eq!(msg.value, "Say Hello");
1053
1054 input.event(&Event::MouseDown {
1056 position: Point::new(300.0, 15.0),
1057 button: MouseButton::Left,
1058 });
1059 assert!(!input.focused);
1060 }
1061
1062 #[test]
1063 fn test_text_input_event_cursor_navigation() {
1064 let mut input = TextInput::new().value("abcde");
1065 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
1066 input.event(&Event::FocusIn);
1067 input.cursor = 2;
1068
1069 input.event(&Event::KeyDown { key: Key::Left });
1071 input.event(&Event::KeyDown { key: Key::Left });
1072 input.event(&Event::KeyDown { key: Key::Left }); assert_eq!(input.cursor, 0);
1074
1075 input.event(&Event::KeyDown { key: Key::End });
1077 input.event(&Event::KeyDown { key: Key::Right }); assert_eq!(input.cursor, 5);
1079 }
1080
1081 #[test]
1082 fn test_text_input_event_backspace_at_start() {
1083 let mut input = TextInput::new().value("hello");
1084 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
1085 input.event(&Event::FocusIn);
1086 input.cursor = 0;
1087
1088 let result = input.event(&Event::KeyDown {
1089 key: Key::Backspace,
1090 });
1091 assert_eq!(input.get_value(), "hello"); assert!(result.is_none()); }
1094
1095 #[test]
1096 fn test_text_input_event_delete_at_end() {
1097 let mut input = TextInput::new().value("hello");
1098 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
1099 input.event(&Event::FocusIn);
1100 input.cursor = 5;
1101
1102 let result = input.event(&Event::KeyDown { key: Key::Delete });
1103 assert_eq!(input.get_value(), "hello"); assert!(result.is_none());
1105 }
1106
1107 #[test]
1108 fn test_text_input_event_max_length_enforced() {
1109 let mut input = TextInput::new().max_length(5);
1110 input.layout(Rect::new(0.0, 0.0, 200.0, 30.0));
1111 input.event(&Event::FocusIn);
1112
1113 input.event(&Event::TextInput {
1115 text: "hello world".to_string(),
1116 });
1117 assert_eq!(input.get_value(), "hello"); }
1119}