1mod editor;
34mod value;
35
36pub mod cursor;
37
38pub use cursor::Cursor;
39pub use value::Value;
40
41use editor::Editor;
42
43use crate::core::alignment;
44use crate::core::clipboard::{self, Clipboard};
45use crate::core::input_method;
46use crate::core::keyboard;
47use crate::core::keyboard::key;
48use crate::core::layout;
49use crate::core::mouse::{self, click};
50use crate::core::renderer;
51use crate::core::text::paragraph::{self, Paragraph as _};
52use crate::core::text::{self, Text};
53use crate::core::time::{Duration, Instant};
54use crate::core::touch;
55use crate::core::widget;
56use crate::core::widget::operation::{self, Operation};
57use crate::core::widget::tree::{self, Tree};
58use crate::core::window;
59use crate::core::{
60 Alignment, Background, Border, Color, Element, Event, InputMethod, Layout,
61 Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector,
62 Widget,
63};
64
65pub struct TextInput<
98 'a,
99 Message,
100 Theme = crate::Theme,
101 Renderer = crate::Renderer,
102> where
103 Theme: Catalog,
104 Renderer: text::Renderer,
105{
106 id: Option<widget::Id>,
107 placeholder: String,
108 value: Value,
109 is_secure: bool,
110 font: Option<Renderer::Font>,
111 width: Length,
112 padding: Padding,
113 size: Option<Pixels>,
114 line_height: text::LineHeight,
115 alignment: alignment::Horizontal,
116 on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
117 on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
118 on_submit: Option<Message>,
119 icon: Option<Icon<Renderer::Font>>,
120 class: Theme::Class<'a>,
121 last_status: Option<Status>,
122}
123
124pub const DEFAULT_PADDING: Padding = Padding::new(5.0);
126
127impl<'a, Message, Theme, Renderer> TextInput<'a, Message, Theme, Renderer>
128where
129 Message: Clone,
130 Theme: Catalog,
131 Renderer: text::Renderer,
132{
133 pub fn new(placeholder: &str, value: &str) -> Self {
136 TextInput {
137 id: None,
138 placeholder: String::from(placeholder),
139 value: Value::new(value),
140 is_secure: false,
141 font: None,
142 width: Length::Fill,
143 padding: DEFAULT_PADDING,
144 size: None,
145 line_height: text::LineHeight::default(),
146 alignment: alignment::Horizontal::Left,
147 on_input: None,
148 on_paste: None,
149 on_submit: None,
150 icon: None,
151 class: Theme::default(),
152 last_status: None,
153 }
154 }
155
156 pub fn id(mut self, id: impl Into<widget::Id>) -> Self {
158 self.id = Some(id.into());
159 self
160 }
161
162 pub fn secure(mut self, is_secure: bool) -> Self {
164 self.is_secure = is_secure;
165 self
166 }
167
168 pub fn on_input(
173 mut self,
174 on_input: impl Fn(String) -> Message + 'a,
175 ) -> Self {
176 self.on_input = Some(Box::new(on_input));
177 self
178 }
179
180 pub fn on_input_maybe(
185 mut self,
186 on_input: Option<impl Fn(String) -> Message + 'a>,
187 ) -> Self {
188 self.on_input = on_input.map(|f| Box::new(f) as _);
189 self
190 }
191
192 pub fn on_submit(mut self, message: Message) -> Self {
195 self.on_submit = Some(message);
196 self
197 }
198
199 pub fn on_submit_maybe(mut self, on_submit: Option<Message>) -> Self {
202 self.on_submit = on_submit;
203 self
204 }
205
206 pub fn on_paste(
209 mut self,
210 on_paste: impl Fn(String) -> Message + 'a,
211 ) -> Self {
212 self.on_paste = Some(Box::new(on_paste));
213 self
214 }
215
216 pub fn on_paste_maybe(
219 mut self,
220 on_paste: Option<impl Fn(String) -> Message + 'a>,
221 ) -> Self {
222 self.on_paste = on_paste.map(|f| Box::new(f) as _);
223 self
224 }
225
226 pub fn font(mut self, font: Renderer::Font) -> Self {
230 self.font = Some(font);
231 self
232 }
233
234 pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self {
236 self.icon = Some(icon);
237 self
238 }
239
240 pub fn width(mut self, width: impl Into<Length>) -> Self {
242 self.width = width.into();
243 self
244 }
245
246 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
248 self.padding = padding.into();
249 self
250 }
251
252 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
254 self.size = Some(size.into());
255 self
256 }
257
258 pub fn line_height(
260 mut self,
261 line_height: impl Into<text::LineHeight>,
262 ) -> Self {
263 self.line_height = line_height.into();
264 self
265 }
266
267 pub fn align_x(
269 mut self,
270 alignment: impl Into<alignment::Horizontal>,
271 ) -> Self {
272 self.alignment = alignment.into();
273 self
274 }
275
276 #[must_use]
278 pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
279 where
280 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
281 {
282 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
283 self
284 }
285
286 #[must_use]
288 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
289 self.class = class.into();
290 self
291 }
292
293 pub fn layout(
297 &mut self,
298 tree: &mut Tree,
299 renderer: &Renderer,
300 limits: &layout::Limits,
301 value: Option<&Value>,
302 ) -> layout::Node {
303 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
304 let value = value.unwrap_or(&self.value);
305
306 let font = self.font.unwrap_or_else(|| renderer.default_font());
307 let text_size = self.size.unwrap_or_else(|| renderer.default_size());
308 let padding = self.padding.fit(Size::ZERO, limits.max());
309 let height = self.line_height.to_absolute(text_size);
310
311 let limits = limits.width(self.width).shrink(padding);
312 let text_bounds = limits.resolve(self.width, height, Size::ZERO);
313
314 let placeholder_text = Text {
315 font,
316 line_height: self.line_height,
317 content: self.placeholder.as_str(),
318 bounds: Size::new(f32::INFINITY, text_bounds.height),
319 size: text_size,
320 align_x: text::Alignment::Default,
321 align_y: alignment::Vertical::Center,
322 shaping: text::Shaping::Advanced,
323 wrapping: text::Wrapping::default(),
324 };
325
326 let _ = state.placeholder.update(placeholder_text);
327
328 let secure_value = self.is_secure.then(|| value.secure());
329 let value = secure_value.as_ref().unwrap_or(value);
330
331 let _ = state.value.update(Text {
332 content: &value.to_string(),
333 ..placeholder_text
334 });
335
336 if let Some(icon) = &self.icon {
337 let mut content = [0; 4];
338
339 let icon_text = Text {
340 line_height: self.line_height,
341 content: icon.code_point.encode_utf8(&mut content) as &_,
342 font: icon.font,
343 size: icon.size.unwrap_or_else(|| renderer.default_size()),
344 bounds: Size::new(f32::INFINITY, text_bounds.height),
345 align_x: text::Alignment::Center,
346 align_y: alignment::Vertical::Center,
347 shaping: text::Shaping::Advanced,
348 wrapping: text::Wrapping::default(),
349 };
350
351 let _ = state.icon.update(icon_text);
352
353 let icon_width = state.icon.min_width();
354
355 let (text_position, icon_position) = match icon.side {
356 Side::Left => (
357 Point::new(
358 padding.left + icon_width + icon.spacing,
359 padding.top,
360 ),
361 Point::new(padding.left, padding.top),
362 ),
363 Side::Right => (
364 Point::new(padding.left, padding.top),
365 Point::new(
366 padding.left + text_bounds.width - icon_width,
367 padding.top,
368 ),
369 ),
370 };
371
372 let text_node = layout::Node::new(
373 text_bounds - Size::new(icon_width + icon.spacing, 0.0),
374 )
375 .move_to(text_position);
376
377 let icon_node =
378 layout::Node::new(Size::new(icon_width, text_bounds.height))
379 .move_to(icon_position);
380
381 layout::Node::with_children(
382 text_bounds.expand(padding),
383 vec![text_node, icon_node],
384 )
385 } else {
386 let text = layout::Node::new(text_bounds)
387 .move_to(Point::new(padding.left, padding.top));
388
389 layout::Node::with_children(text_bounds.expand(padding), vec![text])
390 }
391 }
392
393 fn input_method<'b>(
394 &self,
395 state: &'b State<Renderer::Paragraph>,
396 layout: Layout<'_>,
397 value: &Value,
398 ) -> InputMethod<&'b str> {
399 let Some(Focus {
400 is_window_focused: true,
401 ..
402 }) = &state.is_focused
403 else {
404 return InputMethod::Disabled;
405 };
406
407 let secure_value = self.is_secure.then(|| value.secure());
408 let value = secure_value.as_ref().unwrap_or(value);
409
410 let text_bounds = layout.children().next().unwrap().bounds();
411
412 let caret_index = match state.cursor.state(value) {
413 cursor::State::Index(position) => position,
414 cursor::State::Selection { start, end } => start.min(end),
415 };
416
417 let text = state.value.raw();
418 let (cursor_x, scroll_offset) =
419 measure_cursor_and_scroll_offset(text, text_bounds, caret_index);
420
421 let alignment_offset = alignment_offset(
422 text_bounds.width,
423 text.min_width(),
424 self.alignment,
425 );
426
427 let x = (text_bounds.x + cursor_x).floor() - scroll_offset
428 + alignment_offset;
429
430 InputMethod::Enabled {
431 position: Point::new(x, text_bounds.y + text_bounds.height),
432 purpose: if self.is_secure {
433 input_method::Purpose::Secure
434 } else {
435 input_method::Purpose::Normal
436 },
437 preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref),
438 }
439 }
440
441 pub fn draw(
446 &self,
447 tree: &Tree,
448 renderer: &mut Renderer,
449 theme: &Theme,
450 layout: Layout<'_>,
451 _cursor: mouse::Cursor,
452 value: Option<&Value>,
453 viewport: &Rectangle,
454 ) {
455 let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
456 let value = value.unwrap_or(&self.value);
457 let is_disabled = self.on_input.is_none();
458
459 let secure_value = self.is_secure.then(|| value.secure());
460 let value = secure_value.as_ref().unwrap_or(value);
461
462 let bounds = layout.bounds();
463
464 let mut children_layout = layout.children();
465 let text_bounds = children_layout.next().unwrap().bounds();
466
467 let style = theme
468 .style(&self.class, self.last_status.unwrap_or(Status::Disabled));
469
470 renderer.fill_quad(
471 renderer::Quad {
472 bounds,
473 border: style.border,
474 ..renderer::Quad::default()
475 },
476 style.background,
477 );
478
479 if self.icon.is_some() {
480 let icon_layout = children_layout.next().unwrap();
481
482 let icon = state.icon.raw();
483
484 renderer.fill_paragraph(
485 icon,
486 icon_layout.bounds().anchor(
487 icon.min_bounds(),
488 Alignment::Center,
489 Alignment::Center,
490 ),
491 style.icon,
492 *viewport,
493 );
494 }
495
496 let text = value.to_string();
497
498 let (cursor, offset, is_selecting) = if let Some(focus) = state
499 .is_focused
500 .as_ref()
501 .filter(|focus| focus.is_window_focused)
502 {
503 match state.cursor.state(value) {
504 cursor::State::Index(position) => {
505 let (text_value_width, offset) =
506 measure_cursor_and_scroll_offset(
507 state.value.raw(),
508 text_bounds,
509 position,
510 );
511
512 let is_cursor_visible = !is_disabled
513 && ((focus.now - focus.updated_at).as_millis()
514 / CURSOR_BLINK_INTERVAL_MILLIS)
515 .is_multiple_of(2);
516
517 let cursor = if is_cursor_visible {
518 Some((
519 renderer::Quad {
520 bounds: Rectangle {
521 x: (text_bounds.x + text_value_width)
522 .floor(),
523 y: text_bounds.y,
524 width: 1.0,
525 height: text_bounds.height,
526 },
527 ..renderer::Quad::default()
528 },
529 style.value,
530 ))
531 } else {
532 None
533 };
534
535 (cursor, offset, false)
536 }
537 cursor::State::Selection { start, end } => {
538 let left = start.min(end);
539 let right = end.max(start);
540
541 let (left_position, left_offset) =
542 measure_cursor_and_scroll_offset(
543 state.value.raw(),
544 text_bounds,
545 left,
546 );
547
548 let (right_position, right_offset) =
549 measure_cursor_and_scroll_offset(
550 state.value.raw(),
551 text_bounds,
552 right,
553 );
554
555 let width = right_position - left_position;
556
557 (
558 Some((
559 renderer::Quad {
560 bounds: Rectangle {
561 x: text_bounds.x + left_position,
562 y: text_bounds.y,
563 width,
564 height: text_bounds.height,
565 },
566 ..renderer::Quad::default()
567 },
568 style.selection,
569 )),
570 if end == right {
571 right_offset
572 } else {
573 left_offset
574 },
575 true,
576 )
577 }
578 }
579 } else {
580 (None, 0.0, false)
581 };
582
583 let draw = |renderer: &mut Renderer, viewport| {
584 let paragraph = if text.is_empty()
585 && state
586 .preedit
587 .as_ref()
588 .map(|preedit| preedit.content.is_empty())
589 .unwrap_or(true)
590 {
591 state.placeholder.raw()
592 } else {
593 state.value.raw()
594 };
595
596 let alignment_offset = alignment_offset(
597 text_bounds.width,
598 paragraph.min_width(),
599 self.alignment,
600 );
601
602 if let Some((cursor, color)) = cursor {
603 renderer.with_translation(
604 Vector::new(alignment_offset - offset, 0.0),
605 |renderer| {
606 renderer.fill_quad(cursor, color);
607 },
608 );
609 } else {
610 renderer.with_translation(Vector::ZERO, |_| {});
611 }
612
613 renderer.fill_paragraph(
614 paragraph,
615 text_bounds.anchor(
616 paragraph.min_bounds(),
617 Alignment::Start,
618 Alignment::Center,
619 ) + Vector::new(alignment_offset - offset, 0.0),
620 if text.is_empty() {
621 style.placeholder
622 } else {
623 style.value
624 },
625 viewport,
626 );
627 };
628
629 if is_selecting {
630 renderer
631 .with_layer(text_bounds, |renderer| draw(renderer, *viewport));
632 } else {
633 draw(renderer, text_bounds);
634 }
635 }
636}
637
638impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
639 for TextInput<'_, Message, Theme, Renderer>
640where
641 Message: Clone,
642 Theme: Catalog,
643 Renderer: text::Renderer,
644{
645 fn tag(&self) -> tree::Tag {
646 tree::Tag::of::<State<Renderer::Paragraph>>()
647 }
648
649 fn state(&self) -> tree::State {
650 tree::State::new(State::<Renderer::Paragraph>::new())
651 }
652
653 fn diff(&self, tree: &mut Tree) {
654 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
655
656 if self.on_input.is_none() {
658 state.is_pasting = None;
659 }
660 }
661
662 fn size(&self) -> Size<Length> {
663 Size {
664 width: self.width,
665 height: Length::Shrink,
666 }
667 }
668
669 fn layout(
670 &mut self,
671 tree: &mut Tree,
672 renderer: &Renderer,
673 limits: &layout::Limits,
674 ) -> layout::Node {
675 self.layout(tree, renderer, limits, None)
676 }
677
678 fn operate(
679 &mut self,
680 tree: &mut Tree,
681 layout: Layout<'_>,
682 _renderer: &Renderer,
683 operation: &mut dyn Operation,
684 ) {
685 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
686
687 operation.text_input(self.id.as_ref(), layout.bounds(), state);
688 operation.focusable(self.id.as_ref(), layout.bounds(), state);
689 }
690
691 fn update(
692 &mut self,
693 tree: &mut Tree,
694 event: &Event,
695 layout: Layout<'_>,
696 cursor: mouse::Cursor,
697 renderer: &Renderer,
698 clipboard: &mut dyn Clipboard,
699 shell: &mut Shell<'_, Message>,
700 _viewport: &Rectangle,
701 ) {
702 let update_cache = |state, value| {
703 replace_paragraph(
704 renderer,
705 state,
706 layout,
707 value,
708 self.font,
709 self.size,
710 self.line_height,
711 );
712 };
713
714 match &event {
715 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
716 | Event::Touch(touch::Event::FingerPressed { .. }) => {
717 let state = state::<Renderer>(tree);
718 let cursor_before = state.cursor;
719
720 let click_position = cursor.position_over(layout.bounds());
721
722 state.is_focused = if click_position.is_some() {
723 let now = Instant::now();
724
725 Some(Focus {
726 updated_at: now,
727 now,
728 is_window_focused: true,
729 })
730 } else {
731 None
732 };
733
734 if let Some(cursor_position) = click_position {
735 let text_layout = layout.children().next().unwrap();
736
737 let target = {
738 let text_bounds = text_layout.bounds();
739
740 let alignment_offset = alignment_offset(
741 text_bounds.width,
742 state.value.raw().min_width(),
743 self.alignment,
744 );
745
746 cursor_position.x - text_bounds.x - alignment_offset
747 };
748
749 let click = mouse::Click::new(
750 cursor_position,
751 mouse::Button::Left,
752 state.last_click,
753 );
754
755 match click.kind() {
756 click::Kind::Single => {
757 let position = if target > 0.0 {
758 let value = if self.is_secure {
759 self.value.secure()
760 } else {
761 self.value.clone()
762 };
763
764 find_cursor_position(
765 text_layout.bounds(),
766 &value,
767 state,
768 target,
769 )
770 } else {
771 None
772 }
773 .unwrap_or(0);
774
775 if state.keyboard_modifiers.shift() {
776 state.cursor.select_range(
777 state.cursor.start(&self.value),
778 position,
779 );
780 } else {
781 state.cursor.move_to(position);
782 }
783 state.is_dragging = true;
784 }
785 click::Kind::Double => {
786 if self.is_secure {
787 state.cursor.select_all(&self.value);
788 } else {
789 let position = find_cursor_position(
790 text_layout.bounds(),
791 &self.value,
792 state,
793 target,
794 )
795 .unwrap_or(0);
796
797 state.cursor.select_range(
798 self.value.previous_start_of_word(position),
799 self.value.next_end_of_word(position),
800 );
801 }
802
803 state.is_dragging = false;
804 }
805 click::Kind::Triple => {
806 state.cursor.select_all(&self.value);
807 state.is_dragging = false;
808 }
809 }
810
811 state.last_click = Some(click);
812
813 if cursor_before != state.cursor {
814 shell.request_redraw();
815 }
816
817 shell.capture_event();
818 }
819 }
820 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
821 | Event::Touch(touch::Event::FingerLifted { .. })
822 | Event::Touch(touch::Event::FingerLost { .. }) => {
823 state::<Renderer>(tree).is_dragging = false;
824 }
825 Event::Mouse(mouse::Event::CursorMoved { position })
826 | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
827 let state = state::<Renderer>(tree);
828
829 if state.is_dragging {
830 let text_layout = layout.children().next().unwrap();
831
832 let target = {
833 let text_bounds = text_layout.bounds();
834
835 let alignment_offset = alignment_offset(
836 text_bounds.width,
837 state.value.raw().min_width(),
838 self.alignment,
839 );
840
841 position.x - text_bounds.x - alignment_offset
842 };
843
844 let value = if self.is_secure {
845 self.value.secure()
846 } else {
847 self.value.clone()
848 };
849
850 let position = find_cursor_position(
851 text_layout.bounds(),
852 &value,
853 state,
854 target,
855 )
856 .unwrap_or(0);
857
858 let selection_before = state.cursor.selection(&value);
859
860 state
861 .cursor
862 .select_range(state.cursor.start(&value), position);
863
864 if let Some(focus) = &mut state.is_focused {
865 focus.updated_at = Instant::now();
866 }
867
868 if selection_before != state.cursor.selection(&value) {
869 shell.request_redraw();
870 }
871
872 shell.capture_event();
873 }
874 }
875 Event::Keyboard(keyboard::Event::KeyPressed {
876 key, text, ..
877 }) => {
878 let state = state::<Renderer>(tree);
879
880 if let Some(focus) = &mut state.is_focused {
881 let modifiers = state.keyboard_modifiers;
882
883 match key.as_ref() {
884 keyboard::Key::Character("c")
885 if state.keyboard_modifiers.command()
886 && !self.is_secure =>
887 {
888 if let Some((start, end)) =
889 state.cursor.selection(&self.value)
890 {
891 clipboard.write(
892 clipboard::Kind::Standard,
893 self.value.select(start, end).to_string(),
894 );
895 }
896
897 shell.capture_event();
898 return;
899 }
900 keyboard::Key::Character("x")
901 if state.keyboard_modifiers.command()
902 && !self.is_secure =>
903 {
904 let Some(on_input) = &self.on_input else {
905 return;
906 };
907
908 if let Some((start, end)) =
909 state.cursor.selection(&self.value)
910 {
911 clipboard.write(
912 clipboard::Kind::Standard,
913 self.value.select(start, end).to_string(),
914 );
915 }
916
917 let mut editor =
918 Editor::new(&mut self.value, &mut state.cursor);
919 editor.delete();
920
921 let message = (on_input)(editor.contents());
922 shell.publish(message);
923 shell.capture_event();
924
925 focus.updated_at = Instant::now();
926 update_cache(state, &self.value);
927 return;
928 }
929 keyboard::Key::Character("v")
930 if state.keyboard_modifiers.command()
931 && !state.keyboard_modifiers.alt() =>
932 {
933 let Some(on_input) = &self.on_input else {
934 return;
935 };
936
937 let content = match state.is_pasting.take() {
938 Some(content) => content,
939 None => {
940 let content: String = clipboard
941 .read(clipboard::Kind::Standard)
942 .unwrap_or_default()
943 .chars()
944 .filter(|c| !c.is_control())
945 .collect();
946
947 Value::new(&content)
948 }
949 };
950
951 let mut editor =
952 Editor::new(&mut self.value, &mut state.cursor);
953 editor.paste(content.clone());
954
955 let message = if let Some(paste) = &self.on_paste {
956 (paste)(editor.contents())
957 } else {
958 (on_input)(editor.contents())
959 };
960 shell.publish(message);
961 shell.capture_event();
962
963 state.is_pasting = Some(content);
964 focus.updated_at = Instant::now();
965 update_cache(state, &self.value);
966 return;
967 }
968 keyboard::Key::Character("a")
969 if state.keyboard_modifiers.command() =>
970 {
971 let cursor_before = state.cursor;
972
973 state.cursor.select_all(&self.value);
974
975 if cursor_before != state.cursor {
976 focus.updated_at = Instant::now();
977
978 shell.request_redraw();
979 }
980
981 shell.capture_event();
982 return;
983 }
984 _ => {}
985 }
986
987 if let Some(text) = text {
988 let Some(on_input) = &self.on_input else {
989 return;
990 };
991
992 state.is_pasting = None;
993
994 if let Some(c) =
995 text.chars().next().filter(|c| !c.is_control())
996 {
997 let mut editor =
998 Editor::new(&mut self.value, &mut state.cursor);
999
1000 editor.insert(c);
1001
1002 let message = (on_input)(editor.contents());
1003 shell.publish(message);
1004 shell.capture_event();
1005
1006 focus.updated_at = Instant::now();
1007 update_cache(state, &self.value);
1008 return;
1009 }
1010 }
1011
1012 match key.as_ref() {
1013 keyboard::Key::Named(key::Named::Enter) => {
1014 if let Some(on_submit) = self.on_submit.clone() {
1015 shell.publish(on_submit);
1016 shell.capture_event();
1017 }
1018 }
1019 keyboard::Key::Named(key::Named::Backspace) => {
1020 let Some(on_input) = &self.on_input else {
1021 return;
1022 };
1023
1024 if modifiers.jump()
1025 && state.cursor.selection(&self.value).is_none()
1026 {
1027 if self.is_secure {
1028 let cursor_pos =
1029 state.cursor.end(&self.value);
1030 state.cursor.select_range(0, cursor_pos);
1031 } else {
1032 state
1033 .cursor
1034 .select_left_by_words(&self.value);
1035 }
1036 }
1037
1038 let mut editor =
1039 Editor::new(&mut self.value, &mut state.cursor);
1040 editor.backspace();
1041
1042 let message = (on_input)(editor.contents());
1043 shell.publish(message);
1044 shell.capture_event();
1045
1046 focus.updated_at = Instant::now();
1047 update_cache(state, &self.value);
1048 }
1049 keyboard::Key::Named(key::Named::Delete) => {
1050 let Some(on_input) = &self.on_input else {
1051 return;
1052 };
1053
1054 if modifiers.jump()
1055 && state.cursor.selection(&self.value).is_none()
1056 {
1057 if self.is_secure {
1058 let cursor_pos =
1059 state.cursor.end(&self.value);
1060 state.cursor.select_range(
1061 cursor_pos,
1062 self.value.len(),
1063 );
1064 } else {
1065 state
1066 .cursor
1067 .select_right_by_words(&self.value);
1068 }
1069 }
1070
1071 let mut editor =
1072 Editor::new(&mut self.value, &mut state.cursor);
1073 editor.delete();
1074
1075 let message = (on_input)(editor.contents());
1076 shell.publish(message);
1077 shell.capture_event();
1078
1079 focus.updated_at = Instant::now();
1080 update_cache(state, &self.value);
1081 }
1082 keyboard::Key::Named(key::Named::Home) => {
1083 let cursor_before = state.cursor;
1084
1085 if modifiers.shift() {
1086 state.cursor.select_range(
1087 state.cursor.start(&self.value),
1088 0,
1089 );
1090 } else {
1091 state.cursor.move_to(0);
1092 }
1093
1094 if cursor_before != state.cursor {
1095 focus.updated_at = Instant::now();
1096
1097 shell.request_redraw();
1098 }
1099
1100 shell.capture_event();
1101 }
1102 keyboard::Key::Named(key::Named::End) => {
1103 let cursor_before = state.cursor;
1104
1105 if modifiers.shift() {
1106 state.cursor.select_range(
1107 state.cursor.start(&self.value),
1108 self.value.len(),
1109 );
1110 } else {
1111 state.cursor.move_to(self.value.len());
1112 }
1113
1114 if cursor_before != state.cursor {
1115 focus.updated_at = Instant::now();
1116
1117 shell.request_redraw();
1118 }
1119
1120 shell.capture_event();
1121 }
1122 keyboard::Key::Named(key::Named::ArrowLeft)
1123 if modifiers.macos_command() =>
1124 {
1125 let cursor_before = state.cursor;
1126
1127 if modifiers.shift() {
1128 state.cursor.select_range(
1129 state.cursor.start(&self.value),
1130 0,
1131 );
1132 } else {
1133 state.cursor.move_to(0);
1134 }
1135
1136 if cursor_before != state.cursor {
1137 focus.updated_at = Instant::now();
1138
1139 shell.request_redraw();
1140 }
1141
1142 shell.capture_event();
1143 }
1144 keyboard::Key::Named(key::Named::ArrowRight)
1145 if modifiers.macos_command() =>
1146 {
1147 let cursor_before = state.cursor;
1148
1149 if modifiers.shift() {
1150 state.cursor.select_range(
1151 state.cursor.start(&self.value),
1152 self.value.len(),
1153 );
1154 } else {
1155 state.cursor.move_to(self.value.len());
1156 }
1157
1158 if cursor_before != state.cursor {
1159 focus.updated_at = Instant::now();
1160
1161 shell.request_redraw();
1162 }
1163
1164 shell.capture_event();
1165 }
1166 keyboard::Key::Named(key::Named::ArrowLeft) => {
1167 let cursor_before = state.cursor;
1168
1169 if modifiers.jump() && !self.is_secure {
1170 if modifiers.shift() {
1171 state
1172 .cursor
1173 .select_left_by_words(&self.value);
1174 } else {
1175 state
1176 .cursor
1177 .move_left_by_words(&self.value);
1178 }
1179 } else if modifiers.shift() {
1180 state.cursor.select_left(&self.value);
1181 } else {
1182 state.cursor.move_left(&self.value);
1183 }
1184
1185 if cursor_before != state.cursor {
1186 focus.updated_at = Instant::now();
1187
1188 shell.request_redraw();
1189 }
1190
1191 shell.capture_event();
1192 }
1193 keyboard::Key::Named(key::Named::ArrowRight) => {
1194 let cursor_before = state.cursor;
1195
1196 if modifiers.jump() && !self.is_secure {
1197 if modifiers.shift() {
1198 state
1199 .cursor
1200 .select_right_by_words(&self.value);
1201 } else {
1202 state
1203 .cursor
1204 .move_right_by_words(&self.value);
1205 }
1206 } else if modifiers.shift() {
1207 state.cursor.select_right(&self.value);
1208 } else {
1209 state.cursor.move_right(&self.value);
1210 }
1211
1212 if cursor_before != state.cursor {
1213 focus.updated_at = Instant::now();
1214
1215 shell.request_redraw();
1216 }
1217
1218 shell.capture_event();
1219 }
1220 keyboard::Key::Named(key::Named::Escape) => {
1221 state.is_focused = None;
1222 state.is_dragging = false;
1223 state.is_pasting = None;
1224
1225 state.keyboard_modifiers =
1226 keyboard::Modifiers::default();
1227
1228 shell.capture_event();
1229 }
1230 _ => {}
1231 }
1232 }
1233 }
1234 Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
1235 let state = state::<Renderer>(tree);
1236
1237 if state.is_focused.is_some()
1238 && let keyboard::Key::Character("v") = key.as_ref()
1239 {
1240 state.is_pasting = None;
1241
1242 shell.capture_event();
1243 }
1244
1245 state.is_pasting = None;
1246 }
1247 Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
1248 let state = state::<Renderer>(tree);
1249
1250 state.keyboard_modifiers = *modifiers;
1251 }
1252 Event::InputMethod(event) => match event {
1253 input_method::Event::Opened | input_method::Event::Closed => {
1254 let state = state::<Renderer>(tree);
1255
1256 state.preedit =
1257 matches!(event, input_method::Event::Opened)
1258 .then(input_method::Preedit::new);
1259
1260 shell.request_redraw();
1261 }
1262 input_method::Event::Preedit(content, selection) => {
1263 let state = state::<Renderer>(tree);
1264
1265 if state.is_focused.is_some() {
1266 state.preedit = Some(input_method::Preedit {
1267 content: content.to_owned(),
1268 selection: selection.clone(),
1269 text_size: self.size,
1270 });
1271
1272 shell.request_redraw();
1273 }
1274 }
1275 input_method::Event::Commit(text) => {
1276 let state = state::<Renderer>(tree);
1277
1278 if let Some(focus) = &mut state.is_focused {
1279 let Some(on_input) = &self.on_input else {
1280 return;
1281 };
1282
1283 let mut editor =
1284 Editor::new(&mut self.value, &mut state.cursor);
1285 editor.paste(Value::new(text));
1286
1287 focus.updated_at = Instant::now();
1288 state.is_pasting = None;
1289
1290 let message = (on_input)(editor.contents());
1291 shell.publish(message);
1292 shell.capture_event();
1293
1294 update_cache(state, &self.value);
1295 }
1296 }
1297 },
1298 Event::Window(window::Event::Unfocused) => {
1299 let state = state::<Renderer>(tree);
1300
1301 if let Some(focus) = &mut state.is_focused {
1302 focus.is_window_focused = false;
1303 }
1304 }
1305 Event::Window(window::Event::Focused) => {
1306 let state = state::<Renderer>(tree);
1307
1308 if let Some(focus) = &mut state.is_focused {
1309 focus.is_window_focused = true;
1310 focus.updated_at = Instant::now();
1311
1312 shell.request_redraw();
1313 }
1314 }
1315 Event::Window(window::Event::RedrawRequested(now)) => {
1316 let state = state::<Renderer>(tree);
1317
1318 if let Some(focus) = &mut state.is_focused
1319 && focus.is_window_focused
1320 {
1321 if matches!(
1322 state.cursor.state(&self.value),
1323 cursor::State::Index(_)
1324 ) {
1325 focus.now = *now;
1326
1327 let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
1328 - (*now - focus.updated_at).as_millis()
1329 % CURSOR_BLINK_INTERVAL_MILLIS;
1330
1331 shell.request_redraw_at(
1332 *now + Duration::from_millis(
1333 millis_until_redraw as u64,
1334 ),
1335 );
1336 }
1337
1338 shell.request_input_method(&self.input_method(
1339 state,
1340 layout,
1341 &self.value,
1342 ));
1343 }
1344 }
1345 _ => {}
1346 }
1347
1348 let state = state::<Renderer>(tree);
1349 let is_disabled = self.on_input.is_none();
1350
1351 let status = if is_disabled {
1352 Status::Disabled
1353 } else if state.is_focused() {
1354 Status::Focused {
1355 is_hovered: cursor.is_over(layout.bounds()),
1356 }
1357 } else if cursor.is_over(layout.bounds()) {
1358 Status::Hovered
1359 } else {
1360 Status::Active
1361 };
1362
1363 if let Event::Window(window::Event::RedrawRequested(_now)) = event {
1364 self.last_status = Some(status);
1365 } else if self
1366 .last_status
1367 .is_some_and(|last_status| status != last_status)
1368 {
1369 shell.request_redraw();
1370 }
1371 }
1372
1373 fn draw(
1374 &self,
1375 tree: &Tree,
1376 renderer: &mut Renderer,
1377 theme: &Theme,
1378 _style: &renderer::Style,
1379 layout: Layout<'_>,
1380 cursor: mouse::Cursor,
1381 viewport: &Rectangle,
1382 ) {
1383 self.draw(tree, renderer, theme, layout, cursor, None, viewport);
1384 }
1385
1386 fn mouse_interaction(
1387 &self,
1388 _state: &Tree,
1389 layout: Layout<'_>,
1390 cursor: mouse::Cursor,
1391 _viewport: &Rectangle,
1392 _renderer: &Renderer,
1393 ) -> mouse::Interaction {
1394 if cursor.is_over(layout.bounds()) {
1395 if self.on_input.is_none() {
1396 mouse::Interaction::Idle
1397 } else {
1398 mouse::Interaction::Text
1399 }
1400 } else {
1401 mouse::Interaction::default()
1402 }
1403 }
1404}
1405
1406impl<'a, Message, Theme, Renderer> From<TextInput<'a, Message, Theme, Renderer>>
1407 for Element<'a, Message, Theme, Renderer>
1408where
1409 Message: Clone + 'a,
1410 Theme: Catalog + 'a,
1411 Renderer: text::Renderer + 'a,
1412{
1413 fn from(
1414 text_input: TextInput<'a, Message, Theme, Renderer>,
1415 ) -> Element<'a, Message, Theme, Renderer> {
1416 Element::new(text_input)
1417 }
1418}
1419
1420#[derive(Debug, Clone)]
1422pub struct Icon<Font> {
1423 pub font: Font,
1425 pub code_point: char,
1427 pub size: Option<Pixels>,
1429 pub spacing: f32,
1431 pub side: Side,
1433}
1434
1435#[derive(Debug, Clone)]
1437pub enum Side {
1438 Left,
1440 Right,
1442}
1443
1444#[derive(Debug, Default, Clone)]
1446pub struct State<P: text::Paragraph> {
1447 value: paragraph::Plain<P>,
1448 placeholder: paragraph::Plain<P>,
1449 icon: paragraph::Plain<P>,
1450 is_focused: Option<Focus>,
1451 is_dragging: bool,
1452 is_pasting: Option<Value>,
1453 preedit: Option<input_method::Preedit>,
1454 last_click: Option<mouse::Click>,
1455 cursor: Cursor,
1456 keyboard_modifiers: keyboard::Modifiers,
1457 }
1459
1460fn state<Renderer: text::Renderer>(
1461 tree: &mut Tree,
1462) -> &mut State<Renderer::Paragraph> {
1463 tree.state.downcast_mut::<State<Renderer::Paragraph>>()
1464}
1465
1466#[derive(Debug, Clone)]
1467struct Focus {
1468 updated_at: Instant,
1469 now: Instant,
1470 is_window_focused: bool,
1471}
1472
1473impl<P: text::Paragraph> State<P> {
1474 pub fn new() -> Self {
1476 Self::default()
1477 }
1478
1479 pub fn is_focused(&self) -> bool {
1481 self.is_focused.is_some()
1482 }
1483
1484 pub fn cursor(&self) -> Cursor {
1486 self.cursor
1487 }
1488
1489 pub fn focus(&mut self) {
1491 let now = Instant::now();
1492
1493 self.is_focused = Some(Focus {
1494 updated_at: now,
1495 now,
1496 is_window_focused: true,
1497 });
1498
1499 self.move_cursor_to_end();
1500 }
1501
1502 pub fn unfocus(&mut self) {
1504 self.is_focused = None;
1505 }
1506
1507 pub fn move_cursor_to_front(&mut self) {
1509 self.cursor.move_to(0);
1510 }
1511
1512 pub fn move_cursor_to_end(&mut self) {
1514 self.cursor.move_to(usize::MAX);
1515 }
1516
1517 pub fn move_cursor_to(&mut self, position: usize) {
1519 self.cursor.move_to(position);
1520 }
1521
1522 pub fn select_all(&mut self) {
1524 self.cursor.select_range(0, usize::MAX);
1525 }
1526}
1527
1528impl<P: text::Paragraph> operation::Focusable for State<P> {
1529 fn is_focused(&self) -> bool {
1530 State::is_focused(self)
1531 }
1532
1533 fn focus(&mut self) {
1534 State::focus(self);
1535 }
1536
1537 fn unfocus(&mut self) {
1538 State::unfocus(self);
1539 }
1540}
1541
1542impl<P: text::Paragraph> operation::TextInput for State<P> {
1543 fn text(&self) -> &str {
1544 if self.value.content().is_empty() {
1545 self.placeholder.content()
1546 } else {
1547 self.value.content()
1548 }
1549 }
1550
1551 fn move_cursor_to_front(&mut self) {
1552 State::move_cursor_to_front(self);
1553 }
1554
1555 fn move_cursor_to_end(&mut self) {
1556 State::move_cursor_to_end(self);
1557 }
1558
1559 fn move_cursor_to(&mut self, position: usize) {
1560 State::move_cursor_to(self, position);
1561 }
1562
1563 fn select_all(&mut self) {
1564 State::select_all(self);
1565 }
1566}
1567
1568fn offset<P: text::Paragraph>(
1569 text_bounds: Rectangle,
1570 value: &Value,
1571 state: &State<P>,
1572) -> f32 {
1573 if state.is_focused() {
1574 let cursor = state.cursor();
1575
1576 let focus_position = match cursor.state(value) {
1577 cursor::State::Index(i) => i,
1578 cursor::State::Selection { end, .. } => end,
1579 };
1580
1581 let (_, offset) = measure_cursor_and_scroll_offset(
1582 state.value.raw(),
1583 text_bounds,
1584 focus_position,
1585 );
1586
1587 offset
1588 } else {
1589 0.0
1590 }
1591}
1592
1593fn measure_cursor_and_scroll_offset(
1594 paragraph: &impl text::Paragraph,
1595 text_bounds: Rectangle,
1596 cursor_index: usize,
1597) -> (f32, f32) {
1598 let grapheme_position = paragraph
1599 .grapheme_position(0, cursor_index)
1600 .unwrap_or(Point::ORIGIN);
1601
1602 let offset = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0);
1603
1604 (grapheme_position.x, offset)
1605}
1606
1607fn find_cursor_position<P: text::Paragraph>(
1610 text_bounds: Rectangle,
1611 value: &Value,
1612 state: &State<P>,
1613 x: f32,
1614) -> Option<usize> {
1615 let offset = offset(text_bounds, value, state);
1616 let value = value.to_string();
1617
1618 let char_offset = state
1619 .value
1620 .raw()
1621 .hit_test(Point::new(x + offset, text_bounds.height / 2.0))
1622 .map(text::Hit::cursor)?;
1623
1624 Some(
1625 unicode_segmentation::UnicodeSegmentation::graphemes(
1626 &value[..char_offset.min(value.len())],
1627 true,
1628 )
1629 .count(),
1630 )
1631}
1632
1633fn replace_paragraph<Renderer>(
1634 renderer: &Renderer,
1635 state: &mut State<Renderer::Paragraph>,
1636 layout: Layout<'_>,
1637 value: &Value,
1638 font: Option<Renderer::Font>,
1639 text_size: Option<Pixels>,
1640 line_height: text::LineHeight,
1641) where
1642 Renderer: text::Renderer,
1643{
1644 let font = font.unwrap_or_else(|| renderer.default_font());
1645 let text_size = text_size.unwrap_or_else(|| renderer.default_size());
1646
1647 let mut children_layout = layout.children();
1648 let text_bounds = children_layout.next().unwrap().bounds();
1649
1650 state.value = paragraph::Plain::new(Text {
1651 font,
1652 line_height,
1653 content: value.to_string(),
1654 bounds: Size::new(f32::INFINITY, text_bounds.height),
1655 size: text_size,
1656 align_x: text::Alignment::Default,
1657 align_y: alignment::Vertical::Center,
1658 shaping: text::Shaping::Advanced,
1659 wrapping: text::Wrapping::default(),
1660 });
1661}
1662
1663const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
1664
1665#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1667pub enum Status {
1668 Active,
1670 Hovered,
1672 Focused {
1674 is_hovered: bool,
1676 },
1677 Disabled,
1679}
1680
1681#[derive(Debug, Clone, Copy, PartialEq)]
1683pub struct Style {
1684 pub background: Background,
1686 pub border: Border,
1688 pub icon: Color,
1690 pub placeholder: Color,
1692 pub value: Color,
1694 pub selection: Color,
1696}
1697
1698pub trait Catalog: Sized {
1700 type Class<'a>;
1702
1703 fn default<'a>() -> Self::Class<'a>;
1705
1706 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
1708}
1709
1710pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
1714
1715impl Catalog for Theme {
1716 type Class<'a> = StyleFn<'a, Self>;
1717
1718 fn default<'a>() -> Self::Class<'a> {
1719 Box::new(default)
1720 }
1721
1722 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
1723 class(self, status)
1724 }
1725}
1726
1727pub fn default(theme: &Theme, status: Status) -> Style {
1729 let palette = theme.extended_palette();
1730
1731 let active = Style {
1732 background: Background::Color(palette.background.base.color),
1733 border: Border {
1734 radius: 2.0.into(),
1735 width: 1.0,
1736 color: palette.background.strong.color,
1737 },
1738 icon: palette.background.weak.text,
1739 placeholder: palette.secondary.base.color,
1740 value: palette.background.base.text,
1741 selection: palette.primary.weak.color,
1742 };
1743
1744 match status {
1745 Status::Active => active,
1746 Status::Hovered => Style {
1747 border: Border {
1748 color: palette.background.base.text,
1749 ..active.border
1750 },
1751 ..active
1752 },
1753 Status::Focused { .. } => Style {
1754 border: Border {
1755 color: palette.primary.strong.color,
1756 ..active.border
1757 },
1758 ..active
1759 },
1760 Status::Disabled => Style {
1761 background: Background::Color(palette.background.weak.color),
1762 value: active.placeholder,
1763 placeholder: palette.background.strongest.color,
1764 ..active
1765 },
1766 }
1767}
1768
1769fn alignment_offset(
1770 text_bounds_width: f32,
1771 text_min_width: f32,
1772 alignment: alignment::Horizontal,
1773) -> f32 {
1774 if text_min_width > text_bounds_width {
1775 0.0
1776 } else {
1777 match alignment {
1778 alignment::Horizontal::Left => 0.0,
1779 alignment::Horizontal::Center => {
1780 (text_bounds_width - text_min_width) / 2.0
1781 }
1782 alignment::Horizontal::Right => text_bounds_width - text_min_width,
1783 }
1784 }
1785}