1#![allow(dead_code)]
8
9use alloc::{
10 string::{String, ToString},
11 vec::Vec,
12};
13
14use super::events::NodeId;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum InputType {
23 #[default]
24 Text,
25 Password,
26 Submit,
27 Hidden,
28 Checkbox,
29 Radio,
30 Button,
31 Reset,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
36pub enum FormMethod {
37 #[default]
38 Get,
39 Post,
40}
41
42#[derive(Debug, Clone, Default)]
44pub struct FormElement {
45 pub node_id: NodeId,
47 pub action: String,
49 pub method: FormMethod,
51 pub inputs: Vec<NodeId>,
53}
54
55impl FormElement {
56 pub fn new(node_id: NodeId, action: &str, method: FormMethod) -> Self {
57 Self {
58 node_id,
59 action: action.to_string(),
60 method,
61 inputs: Vec::new(),
62 }
63 }
64
65 pub fn add_input(&mut self, input_node: NodeId) {
67 self.inputs.push(input_node);
68 }
69
70 pub fn encode_form_data(&self, inputs: &[InputElement]) -> String {
72 let mut result = String::new();
73 for input in inputs {
74 if input.input_type == InputType::Submit
75 || input.input_type == InputType::Button
76 || input.input_type == InputType::Reset
77 {
78 continue;
79 }
80 if input.input_type == InputType::Checkbox && !input.checked {
81 continue;
82 }
83 if !result.is_empty() {
84 result.push('&');
85 }
86 result.push_str(&url_encode(&input.name));
87 result.push('=');
88 result.push_str(&url_encode(&input.value));
89 }
90 result
91 }
92}
93
94#[derive(Debug, Clone, Default)]
96pub struct InputElement {
97 pub node_id: NodeId,
99 pub input_type: InputType,
101 pub name: String,
103 pub value: String,
105 pub checked: bool,
107 pub cursor_pos: usize,
109 pub focused: bool,
111 pub placeholder: String,
113 pub disabled: bool,
115 pub max_length: usize,
117}
118
119impl InputElement {
120 pub fn new(node_id: NodeId, input_type: InputType, name: &str) -> Self {
121 Self {
122 node_id,
123 input_type,
124 name: name.to_string(),
125 value: String::new(),
126 checked: false,
127 cursor_pos: 0,
128 focused: false,
129 placeholder: String::new(),
130 disabled: false,
131 max_length: 0,
132 }
133 }
134
135 pub fn set_value(&mut self, value: &str) {
137 self.value = value.to_string();
138 if self.cursor_pos > self.value.len() {
139 self.cursor_pos = self.value.len();
140 }
141 }
142
143 pub fn toggle_checked(&mut self) {
145 if self.input_type == InputType::Checkbox || self.input_type == InputType::Radio {
146 self.checked = !self.checked;
147 }
148 }
149
150 pub fn display_text(&self) -> String {
152 if self.value.is_empty() && !self.placeholder.is_empty() {
153 return self.placeholder.clone();
154 }
155 match self.input_type {
156 InputType::Password => {
157 let mut masked = String::with_capacity(self.value.len());
158 for _ in 0..self.value.len() {
159 masked.push('*');
160 }
161 masked
162 }
163 _ => self.value.clone(),
164 }
165 }
166}
167
168#[derive(Debug, Clone, Default)]
174pub struct TextInput {
175 pub buffer: String,
177 pub cursor: usize,
179 pub selection_start: Option<usize>,
181 pub selection_end: Option<usize>,
183}
184
185impl TextInput {
186 pub fn new() -> Self {
187 Self::default()
188 }
189
190 pub fn from_text(text: &str) -> Self {
191 Self {
192 buffer: text.to_string(),
193 cursor: text.len(),
194 selection_start: None,
195 selection_end: None,
196 }
197 }
198
199 pub fn insert_char(&mut self, ch: char) {
201 self.delete_selection();
202 if self.cursor > self.buffer.len() {
203 self.cursor = self.buffer.len();
204 }
205 self.buffer.insert(self.cursor, ch);
206 self.cursor += ch.len_utf8();
207 }
208
209 pub fn insert_str(&mut self, s: &str) {
211 self.delete_selection();
212 if self.cursor > self.buffer.len() {
213 self.cursor = self.buffer.len();
214 }
215 self.buffer.insert_str(self.cursor, s);
216 self.cursor += s.len();
217 }
218
219 pub fn backspace(&mut self) -> bool {
221 if self.delete_selection() {
222 return true;
223 }
224 if self.cursor == 0 {
225 return false;
226 }
227 let prev = self.prev_char_boundary(self.cursor);
229 self.buffer.drain(prev..self.cursor);
230 self.cursor = prev;
231 true
232 }
233
234 pub fn delete(&mut self) -> bool {
236 if self.delete_selection() {
237 return true;
238 }
239 if self.cursor >= self.buffer.len() {
240 return false;
241 }
242 let next = self.next_char_boundary(self.cursor);
243 self.buffer.drain(self.cursor..next);
244 true
245 }
246
247 pub fn move_left(&mut self) {
249 self.clear_selection();
250 if self.cursor > 0 {
251 self.cursor = self.prev_char_boundary(self.cursor);
252 }
253 }
254
255 pub fn move_right(&mut self) {
257 self.clear_selection();
258 if self.cursor < self.buffer.len() {
259 self.cursor = self.next_char_boundary(self.cursor);
260 }
261 }
262
263 pub fn move_home(&mut self) {
265 self.clear_selection();
266 self.cursor = 0;
267 }
268
269 pub fn move_end(&mut self) {
271 self.clear_selection();
272 self.cursor = self.buffer.len();
273 }
274
275 pub fn select_all(&mut self) {
277 self.selection_start = Some(0);
278 self.selection_end = Some(self.buffer.len());
279 self.cursor = self.buffer.len();
280 }
281
282 pub fn selected_text(&self) -> Option<&str> {
284 match (self.selection_start, self.selection_end) {
285 (Some(start), Some(end)) if start != end => {
286 let (s, e) = if start < end {
287 (start, end)
288 } else {
289 (end, start)
290 };
291 Some(&self.buffer[s..e])
292 }
293 _ => None,
294 }
295 }
296
297 pub fn delete_selection(&mut self) -> bool {
299 match (self.selection_start, self.selection_end) {
300 (Some(start), Some(end)) if start != end => {
301 let (s, e) = if start < end {
302 (start, end)
303 } else {
304 (end, start)
305 };
306 self.buffer.drain(s..e);
307 self.cursor = s;
308 self.clear_selection();
309 true
310 }
311 _ => false,
312 }
313 }
314
315 pub fn clear_selection(&mut self) {
317 self.selection_start = None;
318 self.selection_end = None;
319 }
320
321 pub fn handle_key(&mut self, key_code: u32, char_code: u32, modifiers: u8) -> bool {
323 let ctrl = modifiers & 2 != 0;
324
325 if ctrl && (key_code == 65 || key_code == 97) {
327 self.select_all();
328 return false;
329 }
330
331 match key_code {
332 8 => self.backspace(), 46 => self.delete(), 37 => {
335 self.move_left();
337 false
338 }
339 39 => {
340 self.move_right();
342 false
343 }
344 36 => {
345 self.move_home();
347 false
348 }
349 35 => {
350 self.move_end();
352 false
353 }
354 _ => {
355 if (32..127).contains(&char_code) {
357 if let Some(ch) = char::from_u32(char_code) {
358 self.insert_char(ch);
359 return true;
360 }
361 }
362 false
363 }
364 }
365 }
366
367 pub fn render_parts(&self) -> (&str, &str) {
370 let pos = self.cursor.min(self.buffer.len());
371 (&self.buffer[..pos], &self.buffer[pos..])
372 }
373
374 fn prev_char_boundary(&self, pos: usize) -> usize {
377 let mut p = pos.saturating_sub(1);
378 while p > 0 && !self.buffer.is_char_boundary(p) {
379 p -= 1;
380 }
381 p
382 }
383
384 fn next_char_boundary(&self, pos: usize) -> usize {
385 let mut p = pos + 1;
386 while p < self.buffer.len() && !self.buffer.is_char_boundary(p) {
387 p += 1;
388 }
389 p.min(self.buffer.len())
390 }
391}
392
393#[derive(Debug, Clone)]
399pub enum LinkAction {
400 Navigate(String),
402 ScrollToAnchor(String),
404 JavaScript(String),
406 None,
408}
409
410pub fn handle_click_on_link(href: &str) -> LinkAction {
412 if href.is_empty() {
413 return LinkAction::None;
414 }
415 if let Some(anchor) = href.strip_prefix('#') {
416 return LinkAction::ScrollToAnchor(anchor.to_string());
417 }
418 if let Some(js) = href.strip_prefix("javascript:") {
419 return LinkAction::JavaScript(js.to_string());
420 }
421 LinkAction::Navigate(href.to_string())
422}
423
424#[derive(Debug, Clone, Default)]
430pub struct ScrollState {
431 pub scroll_y: i32,
433 pub max_scroll_y: i32,
435 pub viewport_height: i32,
437 pub content_height: i32,
439 pub scroll_x: i32,
441 pub max_scroll_x: i32,
443 pub viewport_width: i32,
445 pub content_width: i32,
447}
448
449impl ScrollState {
450 pub fn new(viewport_width: i32, viewport_height: i32) -> Self {
451 Self {
452 viewport_width,
453 viewport_height,
454 ..Default::default()
455 }
456 }
457
458 pub fn set_content_size(&mut self, width: i32, height: i32) {
460 self.content_width = width;
461 self.content_height = height;
462 self.max_scroll_y = (height - self.viewport_height).max(0);
463 self.max_scroll_x = (width - self.viewport_width).max(0);
464 self.clamp();
465 }
466
467 pub fn set_viewport_size(&mut self, width: i32, height: i32) {
469 self.viewport_width = width;
470 self.viewport_height = height;
471 self.max_scroll_y = (self.content_height - height).max(0);
472 self.max_scroll_x = (self.content_width - width).max(0);
473 self.clamp();
474 }
475
476 pub fn scroll_by(&mut self, dx: i32, dy: i32) {
478 self.scroll_x += dx;
479 self.scroll_y += dy;
480 self.clamp();
481 }
482
483 pub fn scroll_to(&mut self, x: i32, y: i32) {
485 self.scroll_x = x;
486 self.scroll_y = y;
487 self.clamp();
488 }
489
490 pub fn ensure_visible_y(&mut self, y: i32, height: i32) {
492 if y < self.scroll_y {
493 self.scroll_y = y;
494 } else if y + height > self.scroll_y + self.viewport_height {
495 self.scroll_y = y + height - self.viewport_height;
496 }
497 self.clamp();
498 }
499
500 pub fn needs_v_scrollbar(&self) -> bool {
502 self.content_height > self.viewport_height
503 }
504
505 pub fn needs_h_scrollbar(&self) -> bool {
507 self.content_width > self.viewport_width
508 }
509
510 pub fn v_scrollbar_thumb(&self, track_height: i32) -> (i32, i32) {
513 if self.content_height <= 0 || !self.needs_v_scrollbar() {
514 return (0, track_height);
515 }
516 let thumb_h =
517 (self.viewport_height as i64 * track_height as i64 / self.content_height as i64) as i32;
518 let thumb_h = thumb_h.max(20); let scrollable = track_height - thumb_h;
520 let thumb_y = if self.max_scroll_y > 0 {
521 (self.scroll_y as i64 * scrollable as i64 / self.max_scroll_y as i64) as i32
522 } else {
523 0
524 };
525 (thumb_y, thumb_h)
526 }
527
528 fn clamp(&mut self) {
529 self.scroll_x = self.scroll_x.clamp(0, self.max_scroll_x);
530 self.scroll_y = self.scroll_y.clamp(0, self.max_scroll_y);
531 }
532}
533
534fn url_encode(s: &str) -> String {
540 let mut result = String::with_capacity(s.len());
541 for b in s.bytes() {
542 match b {
543 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
544 result.push(b as char);
545 }
546 b' ' => result.push('+'),
547 _ => {
548 result.push('%');
549 result.push(hex_upper((b >> 4) & 0x0F));
550 result.push(hex_upper(b & 0x0F));
551 }
552 }
553 }
554 result
555}
556
557fn hex_upper(n: u8) -> char {
558 if n < 10 {
559 (b'0' + n) as char
560 } else {
561 (b'A' + n - 10) as char
562 }
563}
564
565#[cfg(test)]
570mod tests {
571 #[allow(unused_imports)]
572 use alloc::vec;
573
574 use super::*;
575
576 #[test]
577 fn test_text_input_insert() {
578 let mut ti = TextInput::new();
579 ti.insert_char('h');
580 ti.insert_char('i');
581 assert_eq!(ti.buffer, "hi");
582 assert_eq!(ti.cursor, 2);
583 }
584
585 #[test]
586 fn test_text_input_insert_str() {
587 let mut ti = TextInput::new();
588 ti.insert_str("hello");
589 assert_eq!(ti.buffer, "hello");
590 assert_eq!(ti.cursor, 5);
591 }
592
593 #[test]
594 fn test_text_input_backspace() {
595 let mut ti = TextInput::from_text("abc");
596 assert!(ti.backspace());
597 assert_eq!(ti.buffer, "ab");
598 assert_eq!(ti.cursor, 2);
599 }
600
601 #[test]
602 fn test_text_input_backspace_empty() {
603 let mut ti = TextInput::new();
604 assert!(!ti.backspace());
605 }
606
607 #[test]
608 fn test_text_input_delete() {
609 let mut ti = TextInput::from_text("abc");
610 ti.cursor = 0;
611 assert!(ti.delete());
612 assert_eq!(ti.buffer, "bc");
613 }
614
615 #[test]
616 fn test_text_input_delete_at_end() {
617 let mut ti = TextInput::from_text("abc");
618 assert!(!ti.delete());
619 }
620
621 #[test]
622 fn test_text_input_move_left_right() {
623 let mut ti = TextInput::from_text("abc");
624 ti.move_left();
625 assert_eq!(ti.cursor, 2);
626 ti.move_left();
627 assert_eq!(ti.cursor, 1);
628 ti.move_right();
629 assert_eq!(ti.cursor, 2);
630 }
631
632 #[test]
633 fn test_text_input_home_end() {
634 let mut ti = TextInput::from_text("hello");
635 ti.move_home();
636 assert_eq!(ti.cursor, 0);
637 ti.move_end();
638 assert_eq!(ti.cursor, 5);
639 }
640
641 #[test]
642 fn test_text_input_select_all() {
643 let mut ti = TextInput::from_text("hello");
644 ti.select_all();
645 assert_eq!(ti.selection_start, Some(0));
646 assert_eq!(ti.selection_end, Some(5));
647 assert_eq!(ti.selected_text(), Some("hello"));
648 }
649
650 #[test]
651 fn test_text_input_delete_selection() {
652 let mut ti = TextInput::from_text("hello world");
653 ti.selection_start = Some(5);
654 ti.selection_end = Some(11);
655 assert!(ti.delete_selection());
656 assert_eq!(ti.buffer, "hello");
657 assert_eq!(ti.cursor, 5);
658 }
659
660 #[test]
661 fn test_text_input_render_parts() {
662 let mut ti = TextInput::from_text("hello");
663 ti.cursor = 3;
664 let (before, after) = ti.render_parts();
665 assert_eq!(before, "hel");
666 assert_eq!(after, "lo");
667 }
668
669 #[test]
670 fn test_input_element_password_display() {
671 let mut ie = InputElement::new(0, InputType::Password, "pw");
672 ie.set_value("secret");
673 assert_eq!(ie.display_text(), "******");
674 }
675
676 #[test]
677 fn test_input_element_placeholder() {
678 let mut ie = InputElement::new(0, InputType::Text, "name");
679 ie.placeholder = "Enter name".to_string();
680 assert_eq!(ie.display_text(), "Enter name");
681 ie.set_value("Alice");
682 assert_eq!(ie.display_text(), "Alice");
683 }
684
685 #[test]
686 fn test_input_element_toggle_checkbox() {
687 let mut ie = InputElement::new(0, InputType::Checkbox, "agree");
688 assert!(!ie.checked);
689 ie.toggle_checked();
690 assert!(ie.checked);
691 ie.toggle_checked();
692 assert!(!ie.checked);
693 }
694
695 #[test]
696 fn test_form_encode() {
697 let form = FormElement::new(0, "/submit", FormMethod::Post);
698 let inputs = vec![
699 {
700 let mut ie = InputElement::new(1, InputType::Text, "user");
701 ie.set_value("alice");
702 ie
703 },
704 {
705 let mut ie = InputElement::new(2, InputType::Text, "pass");
706 ie.set_value("s&cr=t");
707 ie
708 },
709 ];
710 let encoded = form.encode_form_data(&inputs);
711 assert!(encoded.contains("user=alice"));
712 assert!(encoded.contains("pass=s%26cr%3Dt"));
713 }
714
715 #[test]
716 fn test_link_navigate() {
717 match handle_click_on_link("https://example.com") {
718 LinkAction::Navigate(url) => assert_eq!(url, "https://example.com"),
719 _ => panic!("expected Navigate"),
720 }
721 }
722
723 #[test]
724 fn test_link_anchor() {
725 match handle_click_on_link("#top") {
726 LinkAction::ScrollToAnchor(a) => assert_eq!(a, "top"),
727 _ => panic!("expected ScrollToAnchor"),
728 }
729 }
730
731 #[test]
732 fn test_link_javascript() {
733 match handle_click_on_link("javascript:alert(1)") {
734 LinkAction::JavaScript(js) => assert_eq!(js, "alert(1)"),
735 _ => panic!("expected JavaScript"),
736 }
737 }
738
739 #[test]
740 fn test_link_empty() {
741 match handle_click_on_link("") {
742 LinkAction::None => {}
743 _ => panic!("expected None"),
744 }
745 }
746
747 #[test]
748 fn test_scroll_state_basic() {
749 let mut s = ScrollState::new(800, 600);
750 s.set_content_size(800, 1200);
751 assert_eq!(s.max_scroll_y, 600);
752 assert!(s.needs_v_scrollbar());
753 assert!(!s.needs_h_scrollbar());
754 }
755
756 #[test]
757 fn test_scroll_by() {
758 let mut s = ScrollState::new(800, 600);
759 s.set_content_size(800, 1200);
760 s.scroll_by(0, 100);
761 assert_eq!(s.scroll_y, 100);
762 s.scroll_by(0, 1000);
763 assert_eq!(s.scroll_y, 600); }
765
766 #[test]
767 fn test_scroll_to() {
768 let mut s = ScrollState::new(800, 600);
769 s.set_content_size(800, 1200);
770 s.scroll_to(0, 300);
771 assert_eq!(s.scroll_y, 300);
772 s.scroll_to(0, -10);
773 assert_eq!(s.scroll_y, 0); }
775
776 #[test]
777 fn test_scrollbar_thumb() {
778 let mut s = ScrollState::new(800, 600);
779 s.set_content_size(800, 1200);
780 let (y, h) = s.v_scrollbar_thumb(600);
781 assert_eq!(h, 300);
783 assert_eq!(y, 0);
784 }
785
786 #[test]
787 fn test_url_encode() {
788 assert_eq!(url_encode("hello"), "hello");
789 assert_eq!(url_encode("a b"), "a+b");
790 assert_eq!(url_encode("a&b=c"), "a%26b%3Dc");
791 }
792
793 #[test]
794 fn test_handle_key_printable() {
795 let mut ti = TextInput::new();
796 let changed = ti.handle_key(65, 65, 0); assert!(changed);
798 assert_eq!(ti.buffer, "A");
799 }
800
801 #[test]
802 fn test_handle_key_backspace() {
803 let mut ti = TextInput::from_text("ab");
804 let changed = ti.handle_key(8, 0, 0);
805 assert!(changed);
806 assert_eq!(ti.buffer, "a");
807 }
808}