Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-widgets/src/input.rs
1//! Text input widget implementation
2//!
3//! Provides text input components with various input types, validation, and formatting options.
4 
5use crate::widget::{generate_id, Widget, WidgetId};
6use std::{any::Any, sync::Arc};
7use strato_core::{
8 event::{Event, EventResult, KeyCode, KeyEvent, KeyboardEvent, MouseEvent},
9 layout::{Constraints, Layout, Size},
10 state::Signal,
11 theme::Theme,
12 types::{Color, Point, Rect, Transform},
13 vdom::VNode,
14};
15use strato_renderer::{
16 batch::RenderBatch,
17 vertex::{Vertex, VertexBuilder},
18};
19 
20/// Input type enumeration
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub enum InputType {
23 Text,
24 Password,
25 Email,
26 Number,
27 Tel,
28 Url,
29 Search,
30 Multiline,
31}
32 
33/// Validation state for input
34#[derive(Debug, Clone, Copy, PartialEq)]
35pub enum ValidationState {
36 Valid,
37 Invalid,
38 Warning,
39 Pending,
40}
41 
42/// Input state enumeration
43#[derive(Debug, Clone, Copy, PartialEq)]
44pub enum InputState {
45 Normal,
46 Focused,
47 Hovered,
48 Disabled,
49 ReadOnly,
50 Error,
51}
52 
53/// Style configuration for input widgets
54#[derive(Debug, Clone)]
55pub struct InputStyle {
56 pub background_color: Color,
57 pub border_color: Color,
58 pub text_color: Color,
59 pub placeholder_color: Color,
60 pub selection_color: Color,
61 pub cursor_color: Color,
62 pub border_width: f32,
63 pub border_radius: f32,
64 pub padding: (f32, f32, f32, f32), // top, right, bottom, left
65 pub font_size: f32,
66 pub font_family: String,
67 pub line_height: f32,
68}
69 
70impl Default for InputStyle {
71 fn default() -> Self {
72 // Use platform-specific default fonts
73 #[cfg(target_os = "windows")]
74 let font_family = "Segoe UI";
75 
76 #[cfg(target_os = "macos")]
77 let font_family = "SF Pro Display";
78 
79 #[cfg(target_os = "linux")]
80 let font_family = "Ubuntu";
81 
82 #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
83 let font_family = "Arial";
84 
85 Self {
86 background_color: Color::WHITE,
87 border_color: Color::GRAY,
88 text_color: Color::BLACK,
89 placeholder_color: Color::LIGHT_GRAY,
90 selection_color: Color::BLUE,
91 cursor_color: Color::BLACK,
92 border_width: 1.0,
93 border_radius: 4.0,
94 padding: (8.0, 12.0, 8.0, 12.0),
95 font_size: 14.0,
96 font_family: font_family.to_string(),
97 line_height: 1.2,
98 }
99 }
100}
101 
102impl InputStyle {
103 /// Create an outlined input style
104 pub fn outlined() -> Self {
105 Self {
106 background_color: Color::WHITE,
107 border_color: Color::GRAY,
108 border_width: 1.0,
109 ..Default::default()
110 }
111 }
112 
113 /// Create a filled input style
114 pub fn filled() -> Self {
115 Self {
116 background_color: Color::LIGHT_GRAY,
117 border_color: Color::TRANSPARENT,
118 border_width: 0.0,
119 ..Default::default()
120 }
121 }
122 
123 /// Get style for a specific input state
124 pub fn for_state(&self, state: InputState) -> Self {
125 let mut style = self.clone();
126 match state {
127 InputState::Focused => {
128 style.border_color = Color::BLUE;
129 }
130 InputState::Hovered => {
131 style.border_color = Color::DARK_GRAY;
132 }
133 InputState::Disabled => {
134 style.background_color = Color::LIGHT_GRAY;
135 style.text_color = Color::GRAY;
136 }
137 InputState::ReadOnly => {
138 style.background_color = Color::LIGHT_GRAY;
139 }
140 InputState::Error => {
141 style.border_color = Color::RED;
142 }
143 _ => {}
144 }
145 style
146 }
147}
148 
149/// Validation function type
150pub type ValidationFn = Box<dyn Fn(&str) -> Result<(), String> + Send + Sync>;
151 
152/// Text input widget
153pub struct TextInput {
154 id: WidgetId,
155 input_type: InputType,
156 value: Signal<String>,
157 placeholder: String,
158 max_length: Option<usize>,
159 min_length: Option<usize>,
160 pattern: Option<String>,
161 required: bool,
162 disabled: Signal<bool>,
163 readonly: Signal<bool>,
164 multiline: bool,
165 rows: usize,
166 cols: usize,
167 
168 // State management
169 state: Signal<InputState>,
170 validation_state: Signal<ValidationState>,
171 validation_message: Signal<Option<String>>,
172 focused: Signal<bool>,
173 hovered: Signal<bool>,
174 
175 // Cursor and selection
176 cursor_position: Signal<usize>,
177 selection_start: Signal<Option<usize>>,
178 selection_end: Signal<Option<usize>>,
179 
180 // Layout and rendering
181 bounds: Signal<Rect>,
182 content_bounds: Signal<Rect>,
183 visible: Signal<bool>,
184 
185 // Styling
186 style: InputStyle,
187 theme: Option<Arc<Theme>>,
188 
189 // Validation
190 validators: Vec<ValidationFn>,
191 
192 // Event handlers
193 on_change: Option<Box<dyn Fn(&str) + Send + Sync>>,
194 on_focus: Option<Box<dyn Fn() + Send + Sync>>,
195 on_blur: Option<Box<dyn Fn() + Send + Sync>>,
196 on_submit: Option<Box<dyn Fn(&str) + Send + Sync>>,
197 
198 // Internal state
199 cursor_blink_timer: Signal<f32>,
200 scroll_offset: Signal<f32>,
201}
202 
203impl std::fmt::Debug for TextInput {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 f.debug_struct("TextInput")
206 .field("id", &self.id)
207 .field("input_type", &self.input_type)
208 .field("value", &self.value)
209 .field("placeholder", &self.placeholder)
210 .field("max_length", &self.max_length)
211 .field("min_length", &self.min_length)
212 .field("pattern", &self.pattern)
213 .field("required", &self.required)
214 .field("disabled", &self.disabled)
215 .field("readonly", &self.readonly)
216 .field("multiline", &self.multiline)
217 .field("rows", &self.rows)
218 .field("cols", &self.cols)
219 .field("state", &self.state)
220 .field("validation_state", &self.validation_state)
221 .field("validation_message", &self.validation_message)
222 .field("focused", &self.focused)
223 .field("hovered", &self.hovered)
224 .field("cursor_position", &self.cursor_position)
225 .field("selection_start", &self.selection_start)
226 .field("selection_end", &self.selection_end)
227 .field("bounds", &self.bounds)
228 .field("content_bounds", &self.content_bounds)
229 .field("visible", &self.visible)
230 .field("style", &self.style)
231 .field("theme", &self.theme)
232 .field(
233 "validators",
234 &format!("{} validators", self.validators.len()),
235 )
236 .field(
237 "on_change",
238 &self.on_change.as_ref().map(|_| "Some(callback)"),
239 )
240 .field(
241 "on_focus",
242 &self.on_focus.as_ref().map(|_| "Some(callback)"),
243 )
244 .field("on_blur", &self.on_blur.as_ref().map(|_| "Some(callback)"))
245 .field(
246 "on_submit",
247 &self.on_submit.as_ref().map(|_| "Some(callback)"),
248 )
249 .field("cursor_blink_timer", &self.cursor_blink_timer)
250 .field("scroll_offset", &self.scroll_offset)
251 .finish()
252 }
253}
254 
255impl TextInput {
256 /// Creates a new text input widget
257 pub fn new() -> Self {
258 Self {
259 id: generate_id(),
260 input_type: InputType::Text,
261 value: Signal::new(String::new()),
262 placeholder: String::new(),
263 max_length: None,
264 min_length: None,
265 pattern: None,
266 required: false,
267 disabled: Signal::new(false),
268 readonly: Signal::new(false),
269 multiline: false,
270 rows: 1,
271 cols: 20,
272 
273 // State management
274 state: Signal::new(InputState::Normal),
275 validation_state: Signal::new(ValidationState::Valid),
276 validation_message: Signal::new(None),
277 focused: Signal::new(false),
278 hovered: Signal::new(false),
279 
280 // Cursor and selection
281 cursor_position: Signal::new(0),
282 selection_start: Signal::new(None),
283 selection_end: Signal::new(None),
284 
285 // Layout and rendering
286 bounds: Signal::new(Rect::new(0.0, 0.0, 0.0, 0.0)),
287 content_bounds: Signal::new(Rect::new(0.0, 0.0, 0.0, 0.0)),
288 visible: Signal::new(true),
289 
290 // Styling
291 style: InputStyle::default(),
292 theme: None,
293 
294 // Validation
295 validators: Vec::new(),
296 
297 // Event handlers
298 on_change: None,
299 on_focus: None,
300 on_blur: None,
301 on_submit: None,
302 
303 // Internal state
304 cursor_blink_timer: Signal::new(0.0),
305 scroll_offset: Signal::new(0.0),
306 }
307 }
308 
309 /// Set input type
310 pub fn input_type(mut self, input_type: InputType) -> Self {
311 self.input_type = input_type;
312 if input_type == InputType::Multiline {
313 self.multiline = true;
314 }
315 self
316 }
317 
318 /// Set placeholder text
319 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
320 self.placeholder = placeholder.into();
321 self
322 }
323 
324 /// Set initial value
325 pub fn value(self, value: impl Into<String>) -> Self {
326 let val = value.into();
327 self.value.set(val);
328 self
329 }
330 
331 /// Set maximum length
332 pub fn max_length(mut self, max_length: usize) -> Self {
333 self.max_length = Some(max_length);
334 self
335 }
336 
337 /// Set minimum length
338 pub fn min_length(mut self, min_length: usize) -> Self {
339 self.min_length = Some(min_length);
340 self
341 }
342 
343 /// Set validation pattern
344 pub fn pattern(mut self, pattern: impl Into<String>) -> Self {
345 self.pattern = Some(pattern.into());
346 self
347 }
348 
349 /// Set required flag
350 pub fn required(mut self, required: bool) -> Self {
351 self.required = required;
352 self
353 }
354 
355 /// Set disabled state
356 pub fn disabled(self, disabled: bool) -> Self {
357 self.disabled.set(disabled);
358 if disabled {
359 self.state.set(InputState::Disabled);
360 }
361 self
362 }
363 
364 /// Set readonly state
365 pub fn readonly(self, readonly: bool) -> Self {
366 self.readonly.set(readonly);
367 if readonly {
368 self.state.set(InputState::ReadOnly);
369 }
370 self
371 }
372 
373 /// Set multiline mode
374 pub fn multiline(mut self, multiline: bool) -> Self {
375 self.multiline = multiline;
376 if multiline {
377 self.input_type = InputType::Multiline;
378 }
379 self
380 }
381 
382 /// Set number of rows (for multiline)
383 pub fn rows(mut self, rows: usize) -> Self {
384 self.rows = rows;
385 self
386 }
387 
388 /// Set number of columns
389 pub fn cols(mut self, cols: usize) -> Self {
390 self.cols = cols;
391 self
392 }
393 
394 /// Set style
395 pub fn style(mut self, style: InputStyle) -> Self {
396 self.style = style;
397 self
398 }
399 
400 /// Set theme
401 pub fn theme(mut self, theme: Arc<Theme>) -> Self {
402 self.theme = Some(theme);
403 self
404 }
405 
406 /// Add validator
407 pub fn validator<F>(mut self, validator: F) -> Self
408 where
409 F: Fn(&str) -> Result<(), String> + Send + Sync + 'static,
410 {
411 self.validators.push(Box::new(validator));
412 self
413 }
414 
415 /// Set change callback
416 pub fn on_change<F>(mut self, callback: F) -> Self
417 where
418 F: Fn(&str) + Send + Sync + 'static,
419 {
420 self.on_change = Some(Box::new(callback));
421 self
422 }
423 
424 /// Set focus callback
425 pub fn on_focus<F>(mut self, callback: F) -> Self
426 where
427 F: Fn() + Send + Sync + 'static,
428 {
429 self.on_focus = Some(Box::new(callback));
430 self
431 }
432 
433 /// Set blur callback
434 pub fn on_blur<F>(mut self, callback: F) -> Self
435 where
436 F: Fn() + Send + Sync + 'static,
437 {
438 self.on_blur = Some(Box::new(callback));
439 self
440 }
441 
442 /// Set submit callback
443 pub fn on_submit<F>(mut self, callback: F) -> Self
444 where
445 F: Fn(&str) + Send + Sync + 'static,
446 {
447 self.on_submit = Some(Box::new(callback));
448 self
449 }
450 
451 /// Gets the widget ID
452 pub fn id(&self) -> WidgetId {
453 self.id
454 }
455 
456 /// Get current value
457 pub fn get_value(&self) -> String {
458 self.value.get()
459 }
460 
461 /// Set value programmatically
462 pub fn set_value(&self, value: impl Into<String>) {
463 let new_value = value.into();
464 
465 // Validate length constraints
466 if let Some(max_len) = self.max_length {
467 if new_value.len() > max_len {
468 return;
469 }
470 }
471 
472 self.value.set(new_value.clone());
473 
474 // Trigger validation
475 self.validate();
476 
477 // Trigger change callback
478 if let Some(ref callback) = self.on_change {
479 callback(&new_value);
480 }
481 }
482 
483 /// Check if input is focused
484 pub fn is_focused(&self) -> bool {
485 self.focused.get()
486 }
487 
488 /// Check if input is disabled
489 pub fn is_disabled(&self) -> bool {
490 self.disabled.get()
491 }
492 
493 /// Check if input is readonly
494 pub fn is_readonly(&self) -> bool {
495 self.readonly.get()
496 }
497 
498 /// Get current selection
499 pub fn get_selection(&self) -> Option<(usize, usize)> {
500 if let (Some(start), Some(end)) = (self.selection_start.get(), self.selection_end.get()) {
501 Some((start, end))
502 } else {
503 None
504 }
505 }
506 
507 /// Set selection
508 pub fn set_selection(&self, start: Option<usize>, end: Option<usize>) {
509 self.selection_start.set(start);
510 self.selection_end.set(end);
511 }
512 
513 /// Clear selection
514 pub fn clear_selection(&self) {
515 self.selection_start.set(None);
516 self.selection_end.set(None);
517 }
518 
519 /// Focus the input
520 pub fn focus(&self) {
521 if !self.is_disabled() && !self.is_readonly() {
522 self.focused.set(true);
523 self.state.set(InputState::Focused);
524 
525 // Trigger focus callback
526 if let Some(ref callback) = self.on_focus {
527 callback();
528 }
529 }
530 }
531 
532 /// Blur the input
533 pub fn blur(&self) {
534 self.focused.set(false);
535 self.clear_selection();
536 
537 // Update state
538 if self.is_disabled() {
539 self.state.set(InputState::Disabled);
540 } else if self.is_readonly() {
541 self.state.set(InputState::ReadOnly);
542 } else if self.validation_state.get() == ValidationState::Invalid {
543 self.state.set(InputState::Error);
544 } else {
545 self.state.set(InputState::Normal);
546 }
547 
548 // Trigger blur callback
549 if let Some(ref callback) = self.on_blur {
550 callback();
551 }
552 }
553 
554 /// Validate input
555 pub fn validate(&self) -> bool {
556 let value = self.value.get();
557 
558 // Check required
559 if self.required && value.is_empty() {
560 self.validation_state.set(ValidationState::Invalid);
561 self.validation_message
562 .set(Some("This field is required".to_string()));
563 return false;
564 }
565 
566 // Check length constraints
567 if let Some(min_len) = self.min_length {
568 if value.len() < min_len {
569 self.validation_state.set(ValidationState::Invalid);
570 self.validation_message
571 .set(Some(format!("Minimum length is {}", min_len)));
572 return false;
573 }
574 }
575 
576 if let Some(max_len) = self.max_length {
577 if value.len() > max_len {
578 self.validation_state.set(ValidationState::Invalid);
579 self.validation_message
580 .set(Some(format!("Maximum length is {}", max_len)));
581 return false;
582 }
583 }
584 
585 // Run custom validators
586 for validator in &self.validators {
587 if let Err(error) = validator(&value) {
588 self.validation_state.set(ValidationState::Invalid);
589 self.validation_message.set(Some(error));
590 return false;
591 }
592 }
593 
594 self.validation_state.set(ValidationState::Valid);
595 self.validation_message.set(None);
596 true
597 }
598 
599 /// Calculate preferred size
600 pub fn calculate_size(&self, available_size: Size) -> Size {
601 let style = self.style.for_state(self.state.get());
602 let padding = style.padding;
603 
604 let text_width = if self.multiline {
605 if available_size.width.is_finite() {
606 available_size.width - padding.1 - padding.3
607 } else {
608 (self.cols as f32) * (style.font_size * 0.6)
609 }
610 } else {
611 (self.cols as f32) * (style.font_size * 0.6) // Approximate character width
612 };
613 
614 let text_height = if self.multiline {
615 (self.rows as f32) * (style.font_size * style.line_height)
616 } else {
617 style.font_size * style.line_height
618 };
619 
620 Size::new(
621 text_width + padding.1 + padding.3,
622 text_height + padding.0 + padding.2,
623 )
624 }
625 
626 /// Layout the input
627 pub fn layout(&self, bounds: Rect) {
628 self.bounds.set(bounds);
629 
630 let style = self.style.for_state(self.state.get());
631 let padding = style.padding;
632 
633 let content_bounds = Rect::new(
634 bounds.x + padding.3,
635 bounds.y + padding.0,
636 bounds.width - padding.1 - padding.3,
637 bounds.height - padding.0 - padding.2,
638 );
639 
640 self.content_bounds.set(content_bounds);
641 }
642 
643 /// Handle mouse events
644 pub fn handle_mouse_event(&self, event: &MouseEvent) -> bool {
645 let bounds = self.bounds.get();
646 let point = Point::new(event.position.x, event.position.y);
647 
648 if !bounds.contains(point) {
649 return false;
650 }
651 
652 match event.button {
653 Some(strato_core::event::MouseButton::Left) => {
654 // For mouse down events, we need to check if this is a press event
655 // Since MouseEvent doesn't have a pressed field, we'll assume this is called for press events
656 self.focus();
657 
658 // Calculate cursor position from click
659 let content_bounds = self.content_bounds.get();
660 let relative_x = point.x - content_bounds.x;
661 
662 // Simple cursor positioning (would need proper text measurement)
663 let char_width = self.style.font_size * 0.6;
664 let cursor_pos = ((relative_x / char_width) as usize).min(self.value.get().len());
665 self.cursor_position.set(cursor_pos);
666 
667 return true;
668 }
669 _ => {}
670 }
671 
672 false
673 }
674 
675 /// Handle keyboard events
676 pub fn handle_key_event(&self, event: &KeyboardEvent) -> bool {
677 if !self.is_focused() || self.is_disabled() || self.is_readonly() {
678 return false;
679 }
680 
681 // Handle text input from KeyboardEvent
682 // NOTE: In many systems, character input comes via Event::TextInput, not KeyboardEvent::text
683 // We keep this for compatibility if the platform sends text here.
684 if let Some(ref text) = event.text {
685 for ch in text.chars() {
686 if ch.is_control() {
687 continue;
688 }
689 self.insert_char(ch);
690 }
691 return true;
692 }
693 
694 // Handle special keys
695 match event.key_code {
696 KeyCode::Backspace => {
697 self.delete_backward();
698 true
699 }
700 KeyCode::Delete => {
701 self.delete_forward();
702 true
703 }
704 KeyCode::Left => {
705 self.move_cursor_left();
706 true
707 }
708 KeyCode::Right => {
709 self.move_cursor_right();
710 true
711 }
712 KeyCode::Enter => {
713 if self.multiline {
714 self.insert_char('\n');
715 } else if let Some(ref callback) = self.on_submit {
716 callback(&self.value.get());
717 }
718 true
719 }
720 KeyCode::Home => {
721 self.cursor_position.set(0);
722 true
723 }
724 KeyCode::End => {
725 self.cursor_position.set(self.value.get().len());
726 true
727 }
728 _ => false,
729 }
730 }
731 
732 /// Insert character at cursor
733 fn insert_char(&self, ch: char) {
734 let mut value = self.value.get();
735 let cursor_pos = self.cursor_position.get();
736 
737 // Check max length
738 if let Some(max_len) = self.max_length {
739 if value.len() >= max_len {
740 return;
741 }
742 }
743 
744 // Insert character
745 if cursor_pos <= value.len() {
746 value.insert(cursor_pos, ch);
747 self.value.set(value.clone());
748 self.cursor_position.set(cursor_pos + 1);
749 
750 // Trigger change callback
751 if let Some(ref callback) = self.on_change {
752 callback(&value);
753 }
754 
755 // Validate
756 self.validate();
757 }
758 }
759 
760 /// Delete character before cursor
761 fn delete_backward(&self) {
762 let mut value = self.value.get();
763 let cursor_pos = self.cursor_position.get();
764 
765 if cursor_pos > 0 && cursor_pos <= value.len() {
766 value.remove(cursor_pos - 1);
767 self.value.set(value.clone());
768 self.cursor_position.set(cursor_pos - 1);
769 
770 // Trigger change callback
771 if let Some(ref callback) = self.on_change {
772 callback(&value);
773 }
774 
775 // Validate
776 self.validate();
777 }
778 }
779 
780 /// Delete character after cursor
781 fn delete_forward(&self) {
782 let mut value = self.value.get();
783 let cursor_pos = self.cursor_position.get();
784 
785 if cursor_pos < value.len() {
786 value.remove(cursor_pos);
787 self.value.set(value.clone());
788 
789 // Trigger change callback
790 if let Some(ref callback) = self.on_change {
791 callback(&value);
792 }
793 
794 // Validate
795 self.validate();
796 }
797 }
798 
799 /// Move cursor left
800 fn move_cursor_left(&self) {
801 let cursor_pos = self.cursor_position.get();
802 if cursor_pos > 0 {
803 self.cursor_position.set(cursor_pos - 1);
804 }
805 }
806 
807 /// Move cursor right
808 fn move_cursor_right(&self) {
809 let cursor_pos = self.cursor_position.get();
810 let value_len = self.value.get().len();
811 if cursor_pos < value_len {
812 self.cursor_position.set(cursor_pos + 1);
813 }
814 }
815 
816 /// Update input (called each frame)
817 pub fn update(&self, delta_time: f32) {
818 // Update cursor blink timer
819 let mut timer = self.cursor_blink_timer.get();
820 timer += delta_time;
821 if timer >= 1.0 {
822 timer = 0.0;
823 }
824 self.cursor_blink_timer.set(timer);
825 }
826 
827 /// Render the input
828 pub fn render(&self, batch: &mut RenderBatch) {
829 let bounds = self.bounds.get();
830 let content_bounds = self.content_bounds.get();
831 let style = self.style.for_state(self.state.get());
832 
833 // Render background
834 batch.add_rect(bounds, style.background_color, Transform::identity());
835 
836 // Render text or placeholder
837 let value = self.value.get();
838 let text_to_render = if value.is_empty() && !self.placeholder.is_empty() {
839 &self.placeholder
840 } else {
841 &value
842 };
843 
844 let text_color = if value.is_empty() && !self.placeholder.is_empty() {
845 style.placeholder_color
846 } else {
847 style.text_color
848 };
849 
850 if !text_to_render.is_empty() {
851 let text_x = content_bounds.x;
852 let text_y = content_bounds.y;
853 batch.add_text(
854 text_to_render.to_string(),
855 (text_x, text_y),
856 text_color,
857 14.0,
858 0.0, // Default letter spacing
859 );
860 }
861 
862 // Render cursor if focused
863 if self.is_focused() && self.cursor_blink_timer.get() < 0.5 {
864 let cursor_pos = self.cursor_position.get();
865 let char_width = style.font_size * 0.6;
866 let cursor_x = content_bounds.x + (cursor_pos as f32) * char_width;
867 
868 batch.add_line(
869 (cursor_x, content_bounds.y),
870 (cursor_x, content_bounds.y + content_bounds.height),
871 style.cursor_color,
872 1.0,
873 );
874 }
875 
876 // Render selection if any
877 if let Some((start, end)) = self.get_selection() {
878 let char_width = style.font_size * 0.6;
879 let selection_start_x = content_bounds.x + (start as f32) * char_width;
880 let selection_end_x = content_bounds.x + (end as f32) * char_width;
881 
882 batch.add_rect(
883 Rect::new(
884 selection_start_x,
885 content_bounds.y,
886 selection_end_x - selection_start_x,
887 content_bounds.height,
888 ),
889 style.selection_color,
890 Transform::identity(),
891 );
892 }
893 }
894 
895 /// Apply theme to input
896 pub fn apply_theme(&mut self, theme: &Theme) {
897 // Apply theme colors to style
898 // This would depend on the Theme structure
899 self.theme = Some(Arc::new(theme.clone()));
900 }
901}
902 
903impl Widget for TextInput {
904 fn id(&self) -> WidgetId {
905 self.id
906 }
907 
908 fn layout(&mut self, constraints: Constraints) -> Size {
909 let available_size = Size::new(constraints.max_width, constraints.max_height);
910 let size = self.calculate_size(available_size);
911 let bounds = Rect::new(0.0, 0.0, size.width, size.height);
912 TextInput::layout(self, bounds);
913 size
914 }
915 
916 fn render(&self, batch: &mut RenderBatch, layout: Layout) {
917 let bounds = Rect::new(
918 layout.position.x,
919 layout.position.y,
920 layout.size.width,
921 layout.size.height,
922 );
923 self.layout(bounds);
924 self.render(batch);
925 }
926 
927 fn handle_event(&mut self, event: &Event) -> EventResult {
928 match event {
929 Event::MouseDown(mouse_event) => {
930 if self.handle_mouse_event(mouse_event) {
931 EventResult::Handled
932 } else {
933 // If we click outside and we were focused, we should blur
934 if self.is_focused() {
935 self.blur();
936 EventResult::Handled // We handled the blur
937 } else {
938 EventResult::Ignored
939 }
940 }
941 }
942 Event::KeyDown(key_event) => {
943 if self.handle_key_event(key_event) {
944 EventResult::Handled
945 } else {
946 EventResult::Ignored
947 }
948 }
949 // Add handling for TextInput events from IME/system
950 Event::TextInput(text) => {
951 if self.is_focused() && !self.is_disabled() && !self.is_readonly() {
952 for ch in text.chars() {
953 if !ch.is_control() {
954 self.insert_char(ch);
955 }
956 }
957 EventResult::Handled
958 } else {
959 EventResult::Ignored
960 }
961 }
962 _ => EventResult::Ignored,
963 }
964 }
965 
966 fn as_any(&self) -> &dyn Any {
967 self
968 }
969 
970 fn as_any_mut(&mut self) -> &mut dyn Any {
971 self
972 }
973 
974 fn clone_widget(&self) -> Box<dyn Widget> {
975 Box::new(self.clone())
976 }
977}
978 
979impl Clone for TextInput {
980 fn clone(&self) -> Self {
981 Self {
982 id: generate_id(), // Generate new ID for clone
983 input_type: self.input_type,
984 value: Signal::new(self.value.get()),
985 placeholder: self.placeholder.clone(),
986 max_length: self.max_length,
987 min_length: self.min_length,
988 pattern: self.pattern.clone(),
989 required: self.required,
990 disabled: Signal::new(self.disabled.get()),
991 readonly: Signal::new(self.readonly.get()),
992 multiline: self.multiline,
993 rows: self.rows,
994 cols: self.cols,
995 state: Signal::new(self.state.get()),
996 validation_state: Signal::new(self.validation_state.get()),
997 validation_message: Signal::new(self.validation_message.get()),
998 focused: Signal::new(self.focused.get()),
999 hovered: Signal::new(self.hovered.get()),
1000 cursor_position: Signal::new(self.cursor_position.get()),
1001 selection_start: Signal::new(self.selection_start.get()),
1002 selection_end: Signal::new(self.selection_end.get()),
1003 bounds: Signal::new(self.bounds.get()),
1004 content_bounds: Signal::new(self.content_bounds.get()),
1005 visible: Signal::new(self.visible.get()),
1006 style: self.style.clone(),
1007 theme: self.theme.clone(),
1008 validators: Vec::new(), // Don't clone validators as they contain closures
1009 on_change: None, // Don't clone event handlers
1010 on_focus: None,
1011 on_blur: None,
1012 on_submit: None,
1013 cursor_blink_timer: Signal::new(self.cursor_blink_timer.get()),
1014 scroll_offset: Signal::new(self.scroll_offset.get()),
1015 }
1016 }
1017}
1018 
1019/// Builder for TextInput
1020pub struct TextInputBuilder {
1021 input: TextInput,
1022}
1023 
1024impl TextInputBuilder {
1025 /// Create new builder
1026 pub fn new() -> Self {
1027 Self {
1028 input: TextInput::new(),
1029 }
1030 }
1031 
1032 /// Set input type
1033 pub fn input_type(mut self, input_type: InputType) -> Self {
1034 self.input = self.input.input_type(input_type);
1035 self
1036 }
1037 
1038 /// Set placeholder
1039 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
1040 self.input = self.input.placeholder(placeholder);
1041 self
1042 }
1043 
1044 /// Set value
1045 pub fn value(mut self, value: impl Into<String>) -> Self {
1046 self.input = self.input.value(value);
1047 self
1048 }
1049 
1050 /// Set required
1051 pub fn required(mut self, required: bool) -> Self {
1052 self.input = self.input.required(required);
1053 self
1054 }
1055 
1056 /// Set disabled
1057 pub fn disabled(mut self, disabled: bool) -> Self {
1058 self.input = self.input.disabled(disabled);
1059 self
1060 }
1061 
1062 /// Add validator
1063 pub fn validator<F>(mut self, validator: F) -> Self
1064 where
1065 F: Fn(&str) -> Result<(), String> + Send + Sync + 'static,
1066 {
1067 self.input = self.input.validator(validator);
1068 self
1069 }
1070 
1071 /// Set change callback
1072 pub fn on_change<F>(mut self, callback: F) -> Self
1073 where
1074 F: Fn(&str) + Send + Sync + 'static,
1075 {
1076 self.input = self.input.on_change(callback);
1077 self
1078 }
1079 
1080 /// Build the input
1081 pub fn build(self) -> TextInput {
1082 self.input
1083 }
1084}
1085 
1086impl Default for TextInputBuilder {
1087 fn default() -> Self {
1088 Self::new()
1089 }
1090}
1091 
1092#[cfg(test)]
1093mod tests {
1094 use super::*;
1095 
1096 #[test]
1097 fn test_input_creation() {
1098 let input = TextInput::new();
1099 assert_eq!(input.get_value(), "");
1100 assert!(!input.is_focused());
1101 }
1102 
1103 #[test]
1104 fn test_input_value() {
1105 let input = TextInput::new().value("test");
1106 assert_eq!(input.get_value(), "test");
1107 }
1108 
1109 #[test]
1110 fn test_input_validation() {
1111 let input = TextInput::new().required(true).validator(|value| {
1112 if value.len() < 3 {
1113 Err("Too short".to_string())
1114 } else {
1115 Ok(())
1116 }
1117 });
1118 
1119 // Empty value should fail validation
1120 assert!(!input.validate());
1121 
1122 // Set valid value
1123 input.set_value("test");
1124 assert!(input.validate());
1125 }
1126 
1127 #[test]
1128 fn test_input_builder() {
1129 let input = TextInputBuilder::new()
1130 .placeholder("Enter text")
1131 .required(true)
1132 .build();
1133 
1134 assert_eq!(input.placeholder, "Enter text");
1135 assert!(input.required);
1136 }
1137}
1138